Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at the best features of ES2017.
Properties of Async Functions
Async functions don’t wrap promises that we return.
For instance, if we have:
async function asyncFunc() {
return Promise.resolve('foo');
}
Then if we have:
asyncFunc()
.then(x => console.log(x))
Then x
is 'foo'
.
If we return rejected promise, then the thrown error value will be in the catch
callback.
For instance, we can write:
async function asyncFunc() {
return Promise.reject(new Error('error'));
}
Then if we write:
asyncFunc()
.catch(err => console.error(err));
and error
is the Error
instance.
Async Function Tips
We should never forget await
if our code on the right side of the =
is a promise.
For example, we shouldn’t write:
async function asyncFunc() {
const value = promiseFunc();
//...
}
That won’t work since there’s no await
to wait for the result of the promise.
await
makes sense even if the function doesn’t return anything.
We can use the promise a signal to pause the function until something is done.
For instance, we can write:
async function asyncFunc() {
await sleep(2000);
//...
}
to make the function pause for 2 seconds with our own sleep
function.
We can implement sleep
however we like to as long as it returns a promise.
We Don’t Always Need await
If we just want to invoke a promise and don’t want to wait for its result, then we don’t need to add await
to it.
For instance, we can write:
async function asyncFunc() {
const writer = openFile('foo.txt');
writer.writeLine('foo');
writer.writeLine('bar');
await writer.close();
}
We didn’t add await
to the writer.writeLine
method calls because we don’t want to wait for the result.
This way, we can make our function run faster since we don’t have to wait.
await
is sequential, Promise.all()
is parallel
We’ve to remember that await
is run sequentially.
And Promise.all
runs the array of promise we pass in in paralle.
So if we have:
async function foo() {
const result1 = await promise1();
const result2 = await promise2();
}
then await
waits for each async function is fulfilled until the next line is run.
On the other hand, if we have:
async function foo() {
const [result1, result2] = await Promise.all([
promise1(),
promise2(),
]);
}
then promise1
and promise2
are run in parallel.
Async Functions and Callbacks
await
only affects the surrounding async function.
So an async function can’t await
in a callback.
However, callbacks can be async functions.
This makes callbacks tricky to use.
For instance, we can’t use await
in a map
callback that isn’t async:
async function download(urls) {
return urls.map(url => {
const content = await makeRequest(url);
return content;
});
}
We don’t have an async
keyword in front of the function.
But we also can’t write:
async function download(urls) {
return urls.map(async url => {
const content = await makeRequest(url);
return content;
});
}
since we won’t be able to get the results of each async function from the callback.
The awaiting is only done inside the callback.
What we should do is to map our URLs to promises and then call Promise.all
on the array of promises.
For instance, we write:
async function download(urls) {
const promiseArray = urls.map(async (url) => {
const content = await makeRequest(url);
return content;
});
return await Promise.all(promiseArray);
}
We call map
to map the array of URLs to an array of promises, and then we return our Promise.all
call to return a promise that resolves to the results of all the promises in an array.
Conclusion
Async functions are handy to use, but it can be misused if we aren’t careful.
So we should make sure to use them properly to avoid unexpected results.