Javascript is awesome, I know that and probably you too. Easy to learn and relatively easy to master as well.
Using higher ordered functions is handy and slick, but sometimes its hard to figure out how to use them asynchronously. Hopefully after reading this post you will understand better when you should await
and how.
The basics
Higher Order Function
Higher order function, according to Wikipedia, is a function that takes one or more functions as an argument and/or returns a function.
The most commonly used functions are:
Array.map
Array.sort
Array.reduce
Array.filter
- ... More array related functions.
Yes, manipulating arrays is in the core of every application, and these nifty little functions are used more than a paper towel in a public restroom.
lets take a look at map
// our array
const arr = [1, 2, 3, 4, 5];
const newArr = arr.map(num => ++num);
// newArr = [2, 3, 4, 5, 6];
This is pretty basic. we have an array of numbers. The goal is to increment every element by one. We use map
to loop though all elements and call our mapping function ((num) => ++num
) passing the element into the function. The mapping function then returns the value of the element plus one. map
itself returns a new array with all the returned values from the mapping function.
So that's easy and straight forward, but what happens if you need to manipulate each element asynchronously. Say you have an array of user ID's, and you want an array of user objects, you might want your mapping function to call an API to fetch each user.
async/await and Promises
I'll assume that you know about JS Promises. If not, well, this post is probably not for you.
What does the async
keyword does? well it automatically wraps the function with a Promise. the returned value will be the resolved argument while throwing an error will be the rejected reason.
async function foo(){
return true;
}
// is basically the same as
function bar(){
return new Promise( (resolve, reject) => {
resolve(true);
});
}
// both can be used the same way
foo()
.then((val)=>{
// Do something with the resolved value
});
bar()
.then((val)=>{
// Do something with the resolved value
});
await
can then be used in a function that uses the async
keyword, therefore no need to chain Promises. It lets you write asynchronous code in a synchronously way. The bottom line is that an async
function always returns a Promise. This is crutial to understanding how to use higher ordered functions asynchronously.
async function foo(){
try{
const resolvedValue = await someAsyncOrPromiseFunction();
// do something with the resolved value
}catch(err){
// err is the rejected reason/error
console.error(err);
}
}
Using Higher ordered functions with async/await
Let's look at a basic async function once more
async function(){
return true;
}
This async function is returning a Promise<boolean>
, Not a boolean.
So lets see what happens when we use an async function to map an array
const arr = [1, 2, 3, 4];
//@returns {Promise}
async function incr(num){
return num++;
}
const results = arr.map(incr);
Since the Array.map
function is creating a new array (based of the original array) with the returned values of the mapping function, we end up with array of promises.
So to actually get the resolved results just need to `await` for each element.
`Promise.all` is what we will use. It expects an array of promises, and returns a single Promise with an array of all resolved values. if one or more were rejected, it will reject as well.
const arr = [1, 2, 3, 4];
//@returns {Promise<number>}
async function incr(num){
return num++;
}
const results = await Promise.all(arr.map(incr));
console.log(results); /// [2, 3, 4, 5]
Let's look at one more commonly used function. Array.reduce
. Lets sum all array values asynchronously. Reduce returns a single value. therefore we only need to await for one Promise. But, because our reducer function is returning a Promise
we need to await for the resolved value. that's why our initial value is inside a Promise.resolve
to mimic the future promises created in each iteration.
const arr = [1, 2, 3, 4, 5];
const reducer = async (accumulator, currentValue) => {
const accum = await accumulator;
return accum + currentValue;
};
const result = await arr.reduce(reducer , Promise.resolve(0));
console.log(result); // 15
Conclusion
Always think about what is being returned from these magical functions, And await accordingly. Since you now understand what is going on when using async/await, you can finally tell your co-workers why their code is incorrect 😉.