Promises to call(you)back: turning Javascript callbacks into promises

This is a very short tutorial on how to turn a Javascript, Node-style callback function into an Angular promise. For me, this came up when I was trying to integrate Stripe.js and Stripe Checkout into an Angular app. Soon, I’ll be writing a longer tutorial on all of the steps required to properly integrate Checkout into an Angular app, but this particular tidbit is relevant to broader use cases.

Stripe.js’s createToken function is used in the following way:

Stripe.card.createToken(card, function (status, response) {
    /* "card" is an object containing the card info */
    if (response.error) {
        // do error stuff
    } else {
        // do non-error stuff
    }
});

Here, we’ve used an anonymous function as the callback, but we could have defined it earlier like Stripe does in their stripe.js tutorial, where they define stripeResponseHandler ahead of its use.

Angular’s documentation on promises and $q is a bit confusing. Ultimately, to create a promise in general, we should do the following bare minimum of work:

  1. Create a “deferred” using $q.defer(). This serves as the object that will create and manage the promise.
  2. We return the promise from our function.
  3. At some point in our function, we need to either “reject” or “resolve” the promise, optionally with arguments. This simply means we let the promise know if it has succeeded or failed, since it won’t know on its own.

While this may seem out of order, it’s important to remember that the whole point of promises is that this is happening asynchronously. When we’re returning the original promise from our function, we’re not returning a result but rather a promise that the next function will eventually get a result.

Here is what the converted function looks like, again using Stripe.js as an example:

var getToken = function (card) {
    // Create the deferred that will generate the promise
    var deferred = $q.defer();

    // Include the original callback function
    Stripe.card.createToken(card, function (status, response) {        
        if (response.error) {
            // If the function fails, reject the promise
            deferred.reject(response.error);                
        } else {
            // If the function succeeds, resolve the promise
            deferred.resolve(response);
        }
    });

    // Return the promise
    return deferred.promise;
};

With this, we can then easily integrate this function into any normal promises flow in an Angular app:

getToken(card).then(function (response) {
    sendTokenToServer(response);
}).catch(function (error) {
    console.error("There's been an error!");
});

In a later post, I’ll go over in greater detail the complete work required to use Stripe Checkout with Angular.