A handful of examples on how to deal with common Promise related situations in JS. If you are not familiar with Promises at all or async in JS this is a very good resource to start: You don't know JS: Async & performance
Wrap callback functions with a new Promise()
to make old
callback code work with Promises.
When dealing with code that uses callbacks for async you need to wrap the callback function with a new Promise instance and resolve/reject it in the callback.
Note: This is really the only time when you need to
use new Promise()
. Once you have a Promise instance,
it will always return another Promise instance when
then()
is called. You can just chain from that.
function myPromiseReturningFn() {
return new Promise((resolve, reject) => {
myCallbackFn((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
myPromiseReturningFn()
.then((result) => {
// do stuff with async results
})
.catch((err) => {
//handle async error
});
then()
and catch()
blocks pretty much work the same way as try/catch
but with async.
Let's assume a situation where we get a user and then get the user's friends. So it's two async calls in sequence.
Since calling then()
on a Promise always generates a new
Promise, we can chain the two async functions nicely.
getUser()
.then((user) => {
// do another async action
return getUsersFriends(user);
})
.then((friends) => {
// do something with friends data.
});
Basic rule here is that when ever you have some async action within
then()
block, return it and chain another then()
. You should never see then()
within another then()
. Don't go into the pyramid of doom.
// same functionality as above, but with nesting
getUser()
.then((user) => {
// do another async action
return getUsersFriends(user)
.then((friends) => {
// do something with friends data.
});
});
Imagine you have 5 async calls you need to do in sequence. The nesting would be unmanageable. We would be back in the old callback pyramids.
Promise.reject() will create a new Promise instance that is already rejected with the given value. We can use that to chain the error handling.
getUser()
.catch((userError) => {
log('getting user failed: ', userError);
// returning a rejection will be caught by the next catch() block.
// then() blocks are skipped.
return Promise.reject(userError);
})
.then((user) => {
return getUsersFriends(user)
.catch((friendsError) => {
log('getting friends failed: ', friendsError);
return Promise.reject(friendsError);
})
})
.then((friends) => {
// do something with friends data.
})
.catch((error) => {
doCleanup();
});
function myAsyncFn() {
return doSmthAsync()
.catch((err) => {
log(err);
return Promise.reject(err);
});
}
myAsyncFn()
.then((results) => {
// do stuff with results
})
.catch((err) => {
// handle error
});
Abstracting a larger chain
function myAsyncFn() {
return doSmthAsync()
.then((results) => {
return doSomeMoreAsync(results);
})
.then((moreResults) => {
return evenMore();
});
}
myAsyncFn()
.then((result) => {
// do stuff
})
.catch((err) => {
// handle error
});
Always return a Promise even if you can return synchronously,
so that the dev using your API doesn't have to worry about sync/async results.
Promise.resolve()
creates a Promise and resolves it immediately with any value passed to it.
getStuff() {
cachedResults = checkCache();
if (cachedResults) {
return Promise.resolve(cachedResults);
}
return getStuffFromServer()
.then((stuff) => {
cacheMyStuff(stuff);
return stuff;
});
}
getStuff()
.then((stuff) =>{
// do stuff with stuff
});
Get multiple things at the same time.
function getStuffAndThings() {
let promises = [
getStuff(),
getThings()
];
// Waits for all Promises to be resolved.
// Rejects if any Promise in the array rejects.
return Promise.all(promises);
}
getStuffAndThings()
.then(([stuff, things]) => {
// do stuff with stuff and things
});
With Promise.all()
and ES6 argument destructing we have a clean way of passing multiple things to the next
then()
block without having to use variables defined in the scope that wraps the chain.
Let's take the previous user and his friends example.
// with scope variable
let user;
getUser()
.then((userData) => {
user = userData
// do another async action
return getUsersFriends(user);
})
.then((friends) => {
doSomethingWith(friends, user);
});
// with Promise.all()
getUser()
.then((user) => {
// do another async action
return Promise.all([getUsersFriends(user), user]);
})
.then(([friends, user]) => {
doSomethingWith(friends, user);
});
Scope variable is not so bad if you have a short chain and you can see the full implementation in one glance, but if the chain is long or you have multiple sets of data you need to keep tabs on, I'd recommend passing data with Promise.all()
.
More great Promise examples: We have a problem with promises
Asynchronous JavaScript in detail: You don't know JS: Async & performance
I highly recommend the whole "You don't know JS" series to anyone wanting to know more about other weird JS mechanics and what you can encounter when you work with JS code bases.
Also when you feel confident with Promises, check out async/await syntax (ES7 only though).