I recently completed the excellent JavaScript Concurrency course on executeprogram.com. Here’s a quick summary of Promises
and async/await
in JavaScript.
What are Promises
?
Promises
are a feature of JavaScript that allows our code to cope with things that might not respond immediately. A classic example of this is waiting for a response from a server, where we don’t know when the data will be returned, or if it will be returned at all.
Handling Promises
Promises
act as a wrapper around another value, which could be any valid JavaScript variable. To access the value contained in a Promises
we can use its then()
method.
then
accepts a function as its single argument. For brevity this is usually passed as an arrow function, for example promise.then(v => console.log(v))
When then
is executed. the value contained in the Promise
is passed into the arrow function and replaced by the result of that function. In the following example we use then
to multiply a Promise
’s value by 2:
const p = Promise.resolve(4);
p.then( v => v * 2 );
p.then( v => console.log(v) );//4
Note that then
calls always returns a promise
which means they can be chained.
Catching errors
If an error is thrown inside a promise it will reject. Its status will change to rejected
and the error will be passed on to the parent scope.
Promise.resolve()
.then(() => {
throw new Error('oops');
});
// Result:
// Uncaught (in promise) Error: oops
// { <state>: rejected, <reason>: Error }
Promises have a method catch
which can be used to intervene, allowing the promise to fulfill with a specified value rather than rejecting.
Promise.resolve() .then(() => { throw new Error('Reject!'); }) .catch(() => 'An error was caught'); // Result: // { <state>: "fulfilled", <value>: 'An error was caught' }
new Promise
& resolve
The new Promise()
constructor accepts a function as a parameter, which is executed immediately. This function can itself accept one or two parameters which, when executed by the Promise constructor, will contain the internal resolve
& reject
methods of the new Promise.
const p = new Promise(
resolve => resolve(4)
);
p.then( v => console.log( v ) );
// Result: 4
const p = new Promise(
(resolve,reject) => reject('oops')
);
// Result:
// Uncaught (in promise) oops
We can use resolve
as a way to play with Promises using setTimeout
:
let resolver;
const p = new Promise( resolve => resolver = resolve )
p.then( v => console.log( v ));
setTimeout( () => resolver('hey'), 1000 );
// Result after 1 second:
// 'hey'
Or more succinctly:
function inOneSecond( func ){
new Promise( resolve => {
setTimeout( () => resolve( func() ), 1000 );
});
}
inOneSecond((()=>console.log('hey'));
// Result after 1 second:
// 'hey'
What is async/await
?
async/await
is a pair of JavaScript keywords to simplify working with Promises
. The async
keyword is used before the function
keyword when defining a function. Functions defined in this way will always return a Promise.
await
is used inside an async
function. It’s prepended to function calls to make the function return a Promise and execute asynchronously.
function inOneSecond( func ) => {
new Promise( resolve => {
setTimeout( () => resolve( func() ), 1000 );
});
}
async function chain() {
await inOneSecond(()=>console.log('1 second'));
await inOneSecond(()=>console.log('2 seconds'));
}
chain();
// Result
// After 1 second: '1 second'
// After 2 seconds: '2 seconds'
Note: This example updates the inOneSecond
function to return the promise it creates
async/await
with arrow functions
The same example using arrow functions:
const inOneSecond = ( func ) => new Promise(
resolve => {
setTimeout( () => resolve( func() ), 1000 );
}
);
const chain = async () => {
await inOneSecond(()=>console.log('1 second'));
await inOneSecond(()=>console.log('2 seconds'));
}
chain();
// Result
// After 1 second: '1 second'
// After 2 seconds: '2 seconds'
Other methods of Promise
Promise.all()
- Takes an array of promises
- Collects their fulfilled values into an array
- Returns a promise fulfilled with that array
- Immediately rejects if any of the promises reject
Promise.all([
Promise.resolve('Resolved promise 1'),
Promise.resolve('Resolved promise 2'),
Promise.resolve('Resolved promise 3')
]).then(
v => console.log( v )
);
// Result: Array(3)
// 0: "Resolved promise 1"
// 1: "Resolved promise 2"
// 2: "Resolved promise 3"
Promise.allSettled()
- Takes an array of promises
- Collects their fulfilled or rejected values
- Returns a promise fulfilled with an array of objects
- Each array object has a
status
and avalue
key:- For fulfilled promises, the
status
isfulfilled
- For rejected promises, the
status
isrejected
- For fulfilled promises, the
Promise.allSettled([
Promise.resolve('Resolved promise 1'),
Promise.reject('Rejected promise 1'),
Promise.resolve('Resolved promise 2'),
Promise.reject('Rejected promise 2')
]).then(
v => console.log( v )
);
// Result: Array(4)
// 0: Object { status: "fulfilled", value: "Resolved promise 1" }
// 1: Object { status: "rejected", reason: "Rejected promise 1" }
// 2: Object { status: "fulfilled", value: "Resolved promise 2" }
// 3: Object { status: "rejected", reason: "Rejected promise 2" }
An example using the Fetch API
fetch( path )
.then(function (response) {
// The API call was successful!
if (response.ok) { return response.json() }
// There was an error
return Promise.reject(response);
})
.then(function (data) {
if ( !data.avatar ) return;
// Add logged-in class to body
document.body.classList.add('logged-in');
})
.catch(function (err) {
// There was an error
console.warn('Something went wrong', err);
});