Simplifying Promises in JavaScript
JavaScript Promises are easy to overcomplicate. Keep them simple and avoid writing unnecessary code.
ES2017’s async/await syntax is already widely adopted among JavaScript developers, but working with Promises directly and cleanly is a necessary underlying skill.
Avoid redundant Promise construction
Here we have an unnecessary Promise being created to wrap the result of a fetch()
:
function getDataAsync(url) {
return new Promise((resolve, reject) => {
const data = fetch(url).then(response => response.json());
resolve(data);
});
}
Because fetch itself is a function that returns a Promise, there’s no need to create a new Promise wrapping its result. The result is already resolved through an existing Promise. We can instead reuse the existing Promise returned from the fetch call:
function getDataAsync(url) {
return fetch(url).then(response => response.json());
}
Whenever we’ve given a Promise, we can use the Promise we’re given. Following from this principle, note that Promise.then()
itself returns a Promise. Even when we return this resulting Promise from a function we’ve implemented, we can continue chaining onto it with subsequent then()
and catch()
calls.
Take an opportunity to simplify
Constructing a Promise is fairly complex. We first need to pass the Promise constructor a function that can accept two callbacks — commonly known as resolve
and reject
, then implement some functionality and resolve on success or reject with an error. Not only is this a good amount to mental overhead, it also involves a certain amount of of syntactic baggage surrounding the functionality we care about.
In the following example, we create a Promise to simulate the act of getting data over a network. Instead of using an existing API that returns a Promise for us, we really do need to create our own:
function getMockDataAsync(shouldSucceed) {
return new Promise((resolve, reject) => {
if (!shouldSucceed) {
reject(new Error('An error occurred.'));
}
resolve({ foo: 'bar' });
});
}
However, there’s a simpler way to create a Promise that immediately resolves or rejects:
function getMockDataAsync(shouldSucceed) {
if (!shouldSucceed) {
return Promise.reject(new Error('An error occurred.'));
}
return Promise.resolve({ foo: 'bar' });
}
Seen here, Promise.resolve()
and Promise.reject()
are static methods on the Promise class that return a Promise and resolve or reject it. These methods are pleasant to use compared to constructing a Promise manually. These methods are great to have on hand when unit testing and mocking Promise-based functionality.