What is ES8 async-await & why it's better than Promise
If you know promise in Javascript, then you should know async-await too, because it is much better than Promise . We know that writing async code that is readable, maintainable and debuggable, has always been challenging. Thanks to async-await, it is a new way to write asynchronous code that looks almost like a synchronous code.
Async-await is fundamentally built on top of promise, So you must have prior knowledge of promise. If not, you can read about the promise in this article
At the time of writing, async-await is not yet the part of ECMAScript standard, but since it has reached stage 4(finished proposal) of the ES8 draft, there are more chances that it will be the part of ES8(ES2017) final release.
Even if its not part of standard yet, async-awiat is supported in and above Chrome55, Firefox52, Edge, Safari10.1, and NodeJS8. But if you are are targeting browser that does not support async-await, then you need to either transpile or polyfill your async-await code using Babel.
Syntax: Promisify
Why are we discussing 'promisify' in an async-await
article? Because async-await
is just syntactic sugar over promise. So the original problem "how to promisify a function" is still the problem: earlier we used to solve it using promise's syntax; now we will solve it using async-await
syntax. In fact whatever problem we solved using promise in this article, we will now solve those problems using async-await
in this article.
Let's take an example: Suppose we want to fetch user profile after successful login. We want to do all these asynchronously(non-blocking way); To do this, we will first get the login token asynchronously using function getLoginToken
and use that login token to fetch user profile asynchronously using function fetchUserProfile
. Assume, getLoginToken
and fetchUserProfile
function is doing its work asynchronously and returning promise.
How To Promisify Function?
See below code on how to promisify function using async-await
and promise
syntax. Notice how we are using keyword async
and await
.
async function getUserProfile() {
let token = await getLoginToken("username", "password")
if (token) {
let usrData = await fetchUserProfile(token)
if (usrData) {
return usrData
} else {
throw new Error("could not fetch userProfile")
}
} else {
throw new Error("Invalid username/password")
}
}
function getUserProfile() {
return new Promise(function(fulfill, reject) {
getLoginToken("username", "password").then(token => {
if (token) {
fetchUserProfile(token).then(usrData => {
usrData ? fulfill(usrData) : reject("could not fetch userProfile")
})
} else {
reject("Invalid username/password")
}
})
})
}
When we put async
keyword before function, it makes that function to behave more like promisified function without using Promise's construct. See below how async
constructed function behave like promisified function.
- Writing
async
keyword before function makes that function to return promise. return
statement insideasync
constructed function makes that function to resolve promise with a return value.throw
statement or any unexpected error insideasync
constructed function makes that function to reject promise with an error.
Putting await
keyword before function tells that function: Hey! please pass me the data when you resolve your promise. You can think of await
like a then
method of promise. See some important point on the usage of await
.
await
keyword can be used only inside anasync
constructed function and it should be prefixed only before those function that does an async operation and return promise.- Using
await
keyword before synchronous function or function that does async opration but does not return promise, might give unexpected result. await
keyword can be used before constant value likevar x = await 2
, but it does not make sense to do so and will have no impact at all.
How To Use Promisified Function?
We have promisified our function getUserProfile
with async
keyword in above section. Now we will see how to use it. There are 2 ways to use async
constructed promisified function: one is using await
keyword and another is using then
method. See below code on how to do this.
wait! in 1st method, why we are using async
constructed IIFE? Because we know, from above section, that await
keyword can only be used inside async
prefixed function.
wait! wait! in 2nd method, how can we use then
method? is it legitimate? Remember! function getUserProfile
is prefixed with async
keyword and from above section, we know that any async
prefixed function is promisfied function, which return promise. So it is legitimate to use then
method here.
;(async function() {
let usrProfile = await getUserProfile()
console.log(usrProfile)
})()
//OR
getUserProfile().then(usrProfile => {
console.log(usrProfile)
})
getUserProfile().then(usrProfile => {
console.log(usrProfile)
})
Limitation: important
So far we saw, how async-await
can solve the problem that promise
can. But can async-await
directly/independently solve these problems without the help of promise? No. See below to know more.
We know that promise can even promisify the function which has event based logic. But can we do the same using 'async-await'? Not at all. For example, can you promisify below function getMyFavouriteDigit
directly using async-await
? No.
function getMyFavouriteDigit() {
setTimeout(() => {
var randomDigit = Math.floor(Math.random() * 9)
if (randomDigit === 6) {
return "i got my favourite digit"
} else {
throw new Error("i could not got my favourite digit")
}
}, 1000)
}
//can be promisified as below using promise syntax
function getMyFavouriteDigit() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
var randomDigit = Math.floor(Math.random() * 9)
if (randomDigit === 6) {
resolve("i got my favourite digit")
} else {
reject("i could not got my favourite digit")
}
}, 1000)
})
}
We also know that 'promise' can directly replace 'callback system' but can 'async-await' directly replace callback. No, Not at all.
The reason for all above is the await
keyword: main clause/term to use 'await' keyword is that await
keyword can only be used before those function that returns a promise and in above example('event based' & callback), there is no concept of promise. So it simply means: we can't use async-await
feature directly or independent of promise
.
async-await
can not even be used, directly/independently, to promisify normal asynchronous function, if all the API used inside that function are not inbuilt promised based API.
Chaining Promises
We know how we use promise chain for interdependent asynchronous function. Now we will see how to implement the same using async-await. In promise, we used to make chain of then
method, but here, we will just put await
keyword before function call.
See the requirement tab: requirement is pretty basic, funcGetText2
should be called only after funcGetText1
is finished as 'funcGetText2 ' is dependent on the result text
returned by 'funcGetText1'. Simillarly, funcGetText3
is dependent on funcGetText2
and printFinalText
is dependent on funcGetText3
.
//Suppose below pseudo code is requirement
//funcGetText1, funcGetText2, funcGetText3 are async
text1 = funcGetText1()
text2 = funcGetText2(text1)
text3 = funcGetText3(text2)
printFinalText(text3)
async function getText() {
let text1 = await funcGetText1()
let text2 = await funcGetText2(text1)
let text3 = await funcGetText3(text2)
printFinalText(text3)
}
getText()
funcGetText1()
.then(funcGetText2)
.then(funcGetText3)
.then(printFinalText)
Array Of Promises
Let's take an example, Where we want to fetch four URLs at the same time. Fetching of one URL is not dependent on another URL, so it has to be done parallelly. We also want to print the result of each URL fetch only when all of the URLs have been fetched. See below code on how to do this.
var promiseArray = []
var urls = ["url1", "url2", "url3", "url4"]
for (var i = 0; i < urls.legth; i++) {
promiseArray.push(fetch(urls[i]))
}
function printResult(result) {
console.log("all urls has been fetched")
for (var i = 0; i < result.length; i++) {
console.log("result for url ", i, " is ", result[i])
}
}
async function fetchAllURLs() {
//see here, how to await array of promise
var result = await Promise.all(promiseArray)
//below is incorrect way to await on array of promise
//var result = await promiseArray;
printResult(result)
}
fetchAllURLs()
var promiseArray = []
var urls = ["url1", "url2", "url3", "url4"]
for (var i = 0; i < urls.length; i++) {
promiseArray.push(fetch(urls[i]))
}
Promise.all(promisArray).then(printResult)
function printResult(result) {
console.log("all urls has been fetched")
for (var i = 0; i < result.length; i++) {
console.log("result for url ", i, " is ", result[i])
}
}
If you notice, we are using promise.all()
in async-await version because there is no simmilar syntax in `async-await' to promisify array of promise.
Please note one difference on the usage of await
as shown in below code: one is non-parallel; another is parallel. See how we are using the 'parallel' version snippet in above code.
Putting await
before fetch
in this way, will make sure that next URL fetch start only when the previous one has finished. This is not what we wanted, this is just too sequential. What we wanted was parallel fetch, not the sequential one.
for(var i=0; i<; i++){
//parallel fetch
promiseArray.push(fetch(urls[i]));
}
//Both looks same, but works quite different
for(var i=0; i<; i++){
// non-parallel fetch
promiseArray.push(await fetch(urls[i]));
}
Error Handling
Any error in a programme must be handled otherwise application can crash and go to inconsistent state. We will see the flow of error handling in async
constructed function. Error handling in async function is just like normal try catch. See below code for an example.
In below code, errorHandler1
will be called when any of funcGetText1
, funcGetText2
and funcGetText3
reject its promise or throw error. While errorHandler2
will be called when any of errorHandler1
, funcGetText4
and printFinalText
reject its promise or throw error.
async function getText() {
try {
try {
await funcGetText1()
await funcGetText2()
await funcGetText3()
} catch (e) {
errorHandler1(e)
}
await funcGetText4()
await printFinalText()
} catch (e) {
errorHanlder2(e)
}
}
getText()
funcGetText1()
.then(funcGetText2)
.then(funcGetText3)
.catch(errorHandler1)
.then(funcGetText4)
.then(printFinalText)
.catch(errorHanlder2)