Resolving Promises
Asynchronicity in JavaScript
Yes, asynchronicity is a word. I verified it with a Google search, which also revealed its increasing importance in the JavaScript realm, as half of the top search results were related to JavaScript. And in JavaScript, asynchronous functions are heavily tied to Promises
.
A Promise
is an object that represents the yet to be determined value of an asynchronous operation. A Promise
has three potential states:
- unfulfilled/pending: the initial state, when a
Promise
has not been rejected but has yet to be fulfilled - fulfilled: the asynchronous operation has reached successful completion and the pending
Promise
can be fulfilled with a value - rejected: the asynchronous operation has failed and the pending
Promise
can return a reason for the error
MDN documentation supplies the following syntax for the Promise
constructor:
new Promise( /* executor */ function(resolve, reject) { ... } );
The executor
function is executed immediately, and usually initiates the asynchronous operation. It takes the resolve
and reject
functions as arguments because, upon completion of that asynchronous operation, it calls resolve
or reject
to change the state of the Promise
. If the Promise
is resolved, it is considered fulfilled and a value can be returned. If the Promise
is rejected, an error can be thrown.
A promise chain can be created by chaining .then()
to a returned Promise
, allowing the return value of a fulfilled Promise
to be passed into subsequent functions. In this chain, .catch()
“catches” errors thrown by a rejected Promise
. Using React and Redux, with thunk middleware, this pattern could be implemented within an action creator as follows:
export const fetchCats = () => {
return dispatch => {
return fetch(URL)
.then(response => response.json())
.then(function(cats) {
let mappedResponse = mapCats(cats);
return mappedResponse
})
.then(async (mappedResponse) => {
let kitties = await Promise.all(mappedResponse.map((cat) => {
return createCat(cat);
}))
// Always return results so they are available to the next element in the promise chain
return kitties
})
.then(function(kitties) {
dispatch(addCats(kitties))
})
.then(function() {
dispatch(clearGame())
})
.then(function() {
dispatch(playGame())
})
.catch(error => window.alert("Not enough kitties!"));
};
};
The example implements a couple other promise-related elements:
async
: Introduced in ES 2017,async
can be prepended to a function to return aPromise
and resolve or reject it as needed (without the explicitresolve
orreject
of thePromise
constructor)await
: Also introduced in ES 2017,await
can be used in conjunction withasync
to pause the execution of theasync
function until thePromise
that followsawait
is resolved. Whileasync
can be used withoutawait
, the latter can only be used within anasync
function.Promise.all
: Is used with iterables and returns a single promise when all promises present in the iterable argument have been resolved. Upon any rejection, it rejects with the reason. The best part,Promise.all
not only returns aPromise
, but preserves the order of the input, if the input is strictly ordered (i.e. an array).
There are a variety of ways to use promises in JavaScript. And the composition of one’s approach largely depends on 1) which version of JavaScript is used throughout the rest of the code-base (always good to maintain consistency), 2) which approach will provide the intended results with the greatest amount of clarity and readability (callbacks can get messy).
For the usage of async
and await
, MDN documentation provides a succinct determiner for usage:
The purpose of
async
/await
functions is to simplify the behavior of using promises synchronously and to perform some behavior on a group ofPromises
. Just asPromises
are similar to structured callbacks,async
/await
is similar to combining generators and promises.
In the example above, there are other ways to work through an array of Promises
but none would be as clean as prepending it with Promise.all
. There is potential for enhancement in the realm of error/rejection handling as more .catch()
statements could be added, to make debugging the promise chain easier. But, otherwise, it’s asynchronicity in action!