7 reasons why you should use async/await today
- Published
For several years node.js programmers have been using callbacks, promises and generators for managing asynchronous functions and honestly that was several years of pain. Every of above approaches had it's own pain points like handling exceptions, chaining, breaking a promise chain etc. But that times are gone. Programmers received async/await and it's highest time to use it for all asynchronous functions. Here is why.
1. Very simple to use
Just prepend your function definition with "async" keyword and voilà.
async function() {
// voilà!
if (someCondition) {
throw new Error('Foo');
}
await someAsyncFunction()
}
// manuall conversion to promises...
function() {
// make sure it always returns a promise (even rejected ones in case of errors)
if (someCondition) {
// throw is not allowed
return Promise.reject(new Error('foo'));
}
return someAsyncFunction()
.then(() => undefined) // in order to achieve exactly the same result as above
}
// extra wrapper which might be a bit problematic for methods definitions
co(function *() {
if (someCondition) {
throw new Error('Foo');
}
yield someAsyncFunction()
})
// I don't even want to remember how to do it with callbacks...
2. Intuitive and easy to learn
Remember How it feels to learn javascript in 2016 ? Yeah. No more excuses for "mistakes" of the past. No need explain why we use callbacks, promises or generator wrappers. Just add 2 extra keywords.
3. Detecting asynchronous functions upfront
There is no way to reliably detect whether a function is asynchronous without inspecting arguments (very unreliable) or calling it (check whether function returns a Thenable) and you don't want to assume every generator as asynchronous.
In the end. Now you able to very clearly (and easy) express your intentions in the code.
const AsyncConstructor = (() => async function() {})().constructor;
someAsyncFunction instanceof AsyncConstructor
// or
Object.prototype.toString.call(someAsyncFunction) === '[object AsyncFunction]'
// or even easier with predicates@2.0.0
const is = require('predicates');
is.asyncFunction(someAsyncFunction);
NOTE: This doesn't work for transpiled async/await that gets "downgraded" to generators.
4. Error handling
With async/await every exception is always "converted" to rejected promise which wasn't a case for callbacks and promises.
async function foo() {
// function below does not exist but TypeError will be converted to rejected promise
someBadReference()
await someAsyncFunction()
}
foo().catch(e => console.log('ok')); // works
function foo() {
// sorry, regular exception will be thrown
someBadReference();
return someAsyncFunction() // hopefully it will always return a promise as well
}
foo().catch(e => console.log('ok')); // doesn't work
function foo(callback) {
// same problem as above
someBadReference();
someCallbackAsyncFunction(callback);
}
foo((e) => console.log('ok')); // doesn't work
co(function *() {
// works as expected => rejected promise
someBadReference();
yield someAsyncFunction();
})().catch((e) => console.log('ok')) // works
5. Easy to use everywhere with Typescript and native node support
Since Typescript 2.1 you can use async/await keywords in every environment.
Since version 7.10 Node supports async/await without any special flag. In that case you can switch Typescript compiler target to "es2017" and async/await won't be "downgraded" to generators.
I 99% sure that "babel" is able to achieve the same but not sure how exactly.
6. Easy to implement multiple code paths
Having multiple code paths wasn't easy with callback and promises only. Just look at amount of questions people had about breaking long promises chain and "sick" solutions for that
google -> break promise chain
// this code is bad and rather stupid, but fine for example purposes
async function() {
try {
const result = await callSomeRemoteAPI();
} catch (e) {
if (e.message.indexOf('not found')) {
return;
}
throw e;
}
for (const entry of result) {
const entryResult = await anotherCallToRemoteAPI(entry);
if (!entryResult) {
return;
}
}
return result;
}
I don't even want to remember how achieve the same with callbacks...
7. Safe to use in external libraries
Again, just because async/await might be transpiled to generators then every library consumer might use it without even noticing, since, in the end, async function still returns a simple promise.
Summary
We've been waiting so long for such powerful feature that makes asynchronous programming easy and pleasant. There is no better tool for that today. Just start using it.