Skip to content

Instantly share code, notes, and snippets.

@asher-dev
Last active December 7, 2018 20:25
Show Gist options
  • Save asher-dev/b5776b04abe30c8d04f6b99a7b736660 to your computer and use it in GitHub Desktop.
Save asher-dev/b5776b04abe30c8d04f6b99a7b736660 to your computer and use it in GitHub Desktop.
Promises in ES6, for asynchronous sequences

A Primer on Promises in ES6

(Post was originally written as a response to a question about how to resolve "callback hell" when asynchronous calls need to be executed sequentially.)

A popular system for managing sequential asynchronous operations, called "Promises", was integrated into ES6 (the latest release version of ECMAScript/Javascript). It's definitely a good idea to make sure you understand basic callbacks before diving into the Promises API, but it can really streamline asynchronous sequences once your callbacks start to get hellish.

It's based on an abstract data type called a "thenable". The basic premise is pretty simple -- a "promise" contains an operation that will be executed asynchronously, and has a .then() method which can be used to specify an operation to follow once the promise is fulfilled. You can create a new promise like this:

// Promise.new() creates a new promise.  It accepts a function as an argument.
let p = new Promise(function(resolve, reject) {  // The function passed in must itself accept two arguments
    // Do something
    if (successCondition) { resolve(1); } // resolve(1) begins a thenable chain and returns the value 1
    else { reject("uh oh"); }  // reject("uh oh") will throw an error that can be caught
});

This simply executes the given function asynchronously. But the p object is now "thenable," meaning you can specify a sequence of events by calling the then method.

p.then(function(val) {
    console.log(val); // prints '1' to the console
});

We can make any number of then method calls on the object p, and they will all be able to use the return value of p and will execute asynchronously.

p.then(function(val) {
    console.log(val); // prints 1
});

p.then(function(val) {
    console.log(val + 1); // prints 2 -- could occur before or after 1 is printed
});

Alternatively, we can create a chain of then calls to specify a sequence of asynchronous operations, and even provide return values to be passed through the sequence.

p.then(function(val) {
    console.log(val); // prints "1" to the console first
    return 'hello';
}).then(function(val2) {
    console.log(val2); // prints "hello" to the console after printing "1"
    return [1, 2, 3];
}).then(function(val3) {
    console.log(val3); // prints "[1, 2, 3]" to the console after printing "hello"
});

We can also catch errors/rejections from promises, using the catch method.

p.catch(function(err) {
    console.log('An error occurred: ');
    console.log(err);  // prints "uh oh" to the console
});

Finally, you can use the Promise.all() function to create a thenable object that will be fulfilled only when all of its constituent promises are fulfilled. Note that below I'm using the ES6 shorthand for anonymous lambda functions, () => x instead of function() { return x; }.

let p1 = new Promise((resolve, reject) => resolve(1));
let p2 = new Promise((resolve, reject) => resolve(2));
let p3 = new Promise((resolve, reject) => resolve(3));

Promise.all([p1, p2, p3]).then((vals) => {
    // vals is an array of the return values of p1, p2, p3
    console.log(vals); // prints "[1, 2, 3]" to the console
    return 'all done';
}).then((val) => console.log(val));  // prints "all done"

There's also Promise.race() function, which is fulfilled as soon as any of its constituent promises is fulfilled, and returns only the value from that promise.

Note that in general, every call to .then() returns another thenable. The method chaining I used in the examples above is typically how it's used, but you can also assign each one to a variable -- especially if you want multiple asynchonous operations to follow the original promise, but their sequence amongst themselves is not important.

let p0 = p.then((val) => console.log(val));  // prints 1
let p1 = p0.then(() => console.log('x'));  // prints "x" after 1
let p2 = p0.then(() => console.log('y'));  // prints "y" after 1

etc.

Another cool feature that serves to further resolve the "callback hell" issue is that if a Promise returns another promise, the returned promise will be resolved before any .then() calls and its return value will be passed forward.

let p = new Promise((resolve, reject) => {
    let pp = new Promise((resolve, reject) => {
        console.log('Inner');
        resolve('Inner resolve');
    });
    console.log('Outer');  // May print 'Outer' before or after printing "Inner"
    resolve(pp);
});

p.then((val) => console.log(val));  // prints "Inner resolve" after both "Inner" and "Outer"

See MDN documentation for Promises at: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment