ECMAScript 2015 Promises
Promises provide a way to escape nested callbacks and provide a neater way to write and reason about asynchronous code.
The syntax was streamlined even further with
async
and await
.
Creating & Writing a Promise
New Promise
instances require two callbacks - one for resolution, and
one for rejection.
const myPromise = new Promise((resolveCallback, rejectCallback) => {
// Doesn't do anything, so returns immediately
resolveCallback('Promise resolved ok.');
})
myPromise.then(resolved => {
console.log(resolved);
});
Normally the body of the Promise
(aka the executor
of the Promise
) will do
something that takes a long time - e.g. an API call.
Using Promises
A Promise
is a thenable
- i.e. you can call then()
with two functions as arguments: the first one handles the success condition, and the second optional
one handles the error condition.
const myPromise = new Promise((resolveCallback, rejectCallback) => {
resolveCallback('Promise resolved ok.');
})
myPromise.then(
// Promise resolved ok
success => console.log(success),
// Promise was rejected
error => console.log(error)
);
Note that the error handler function is optional. If you only care about
errors, you can pass null
for the success handler, or you can use the much
clearer catch()
approach - both of these approaches are identical:
Using catch(fn)
:
const myPromise = new Promise((resolveCallback, rejectCallback) => {
rejectCallback('Promise was rejected');
})
myPromise.catch(
// Promise was rejected
error => console.log(error)
);
Using then(null, fn)
:
const myPromise = new Promise((resolveCallback, rejectCallback) => {
rejectCallback('Promise was rejected');
})
myPromise.then(
null,
// Promise was rejected
error => console.log(error)
);
If you need to do any clean-up, you can use the finally()
function to do that:
const myPromise = new Promise((resolveCallback, rejectCallback) => {
resolveCallback('Promise resolved ok.');
})
myPromise.then(
// Promise resolved ok
success => console.log(success),
// Promise was rejected
error => console.log(error)
).finally (
() => console.log('All done')
);
Note that finally
still expects a callback function, but this one has no
arguments.
Chaining
If a Promise
returns another Promise
, then you can chain them together
sequentially one after the other with just additional then()
handlers.
If you need parallel and not sequential execution, see the ‘Calling
Promises
in parallel` section in this page.
For example imagine a function that loads files from the internet, and you want to load several images
function loadResource(url) {
return new Promise((resolveCallback, rejectCallback) => {
let resource = '...';
// ... do something to retrieve a resource over a slow network connection
// then resolve the promise when it loaded ok
resolveCallback(resource);
});
}
loadResource('http://example.com/image1.png').then(success => {
// Return a new promise to load the second
return loadResource('http://example.com/image2.png')
}).then(success => {
// Return a new promise to load the third
return loadResource('http://example.com/image3.png')
}).then(success => {
console.log('All images loaded ok');
}).catch(error => {
console.error('Error loading our files!');
});
Note that this is synchronous - i.e. you load image1.png
then image2.png
then image3.png
one after the other.
Calling Promises
in parallel
To avoid the pitfals of sequential chaining of promises, you can easily just
call all of your promises in parallell and wait for them to complete using
Promise.all
:
function loadResource(url) {
return new Promise((resolveCallback, rejectCallback) => {
let resource = '...';
// ... do something to retrieve a resource over a slow network connection
// then resolve the promise when it loaded ok
resolveCallback(resource);
});
}
Promise.all([
loadResource('http://example.com/image1.png'),
loadResource('http://example.com/image2.png'),
loadResource('http://example.com/image3.png'),
]).then(success => {
console.log('All images loaded ok');
}).catch(error => {
console.error('Error loading our files!');
});
This executes all of the promises in parallel, and returns its own Promise
that will resolve when all child Promises
resolve - however it is “all or
nothing” in that if a single Promise
rejects, then the outer Promise
also
immediately rejects too and the other child Promises
are simply forgotten
about and their responses ignored. I.e. we need all of the Promises
to
succeed.
There are some other nifty Promise
APIs that can help with parallel execution:
Promise.allSettled([...])
- similar toPromise.all
, but instead of requiring everyPromise
to be successful, this just waits for allPromises
to settle (regardless of success or failure) and returns an array of objects of either:{status:"fulfilled", value:result}
for successes.{status:"rejected", reason:error}
for failures.
Promise.race([...])
- deliberately creates a race condition between two or morePromises
and resolves with the result or error of the first (and only first)Promise
to settle.