After this lesson you will be able to:
- Understand the javascript asynchrony history.
- Understand why async/await it’s important.
- Explain the difference between old asynchrony handling and async-await.
- Solve pending promises with async-await.
As you know javascript it’s an asynchronous programming language, that means that javascript can perform actions (code executions) with timing difference. In other words asynchrony means the difference between now and later.
With that in mind, let’s talk about the mechanisms for handle async code, code that will do something in the future. In javascript history we’ve had several ways to handle this async code as Callbacks and Promises.
Callbacks are a functions which are passing as an argument, and its invoke inside the function for complete an action. That’s the proper definition but callbacks also helps when we need to solve async code, when an async operation has a result , (a result being either returned data or an error that occurred during the operation), it points to a function that will be invoked once that result is ready.
function sayHello(name){
alert(`Hello ${name}`)
}
function getUserName(callback){
const name = prompt(`what's your name?`)
callback(name)
}
getUserName(sayHello)
In the previous code we have two functions the first one it’s called sayHello and only takes a name to execute an alert with it, the second one it’s a function that takes a function as an argument (a callback), prompts the user name then invoke the callback function with the name.
This is great, but let’s take a look into the pros and cons of handle async code with callbacks.
- Callbacks are an easy way to solve this async operations.
- There are a lot of libraries written with callbacks.
- Callbacks have a hard to read structure: this means that callbacks can easily confuse other developers and their structure make us read callbacks sequentially even when there are no sequence.
doA(() => {
doB()
doC(() => {
doD()
})
doE()
})
doF()
When you see the previous code you can easily think that the execution flow follows this sequence: A, B, C, D, E and F. But… no.
first(() => {
third()
fourth(() => {
sixth()
})
fifth()
})
second()
This code above show the real execution flow.
- Callbacks have a common problem called callback hell which is a nested callback execution queue:
functionA(() => {
functionB(() => {
functionC(() => {
functionE(() => {
functionN(){...}
})
})
})
})
- Handmade error handling:
myFunction(num, function callback(err, result) {
if (err) {
return myFunction(num, callback);
}
// handle result
})
Promises are objects used for async processes. A promise have a value that can be available now, in the future or never.
new Promise((resolve, reject) => {
if(true){
resolve(/* Response */)
}else{
reject(/* Error */)
}
})
As you can see the promise receives a callback function with two arguments: resolve and reject, used to make succeed the promise or reject the promise.
A promise is found in one of the following states:
- Pending (pending): initial status, not met or rejected.
- Completed: means that the operation completed successfully.
- Rejected (rejected): means that the operation failed.
To use a promise result you could use the then
and catch
methods.
promise
.then(response => console.log(response))
.catch(error => console.error(error))
This is how we solve promises, then
and catch
methods needs a callback to work, then
receives an argument with the value returned from the resolve method parameter. catch
method also receives the error from the reject method.
This approach looks so difficult, but in most cases you will just solve already created promises from libraries built with promises support for their async operations. For example libraries for Ajax calls, db connection or filesystem.
- It’s easy to use (easier than callbacks).
- Most libraries are now built in promises.
- Easy error handling.
- Sequential execution.
- Return promises that can be chained.
- Have methods to multiple promises or resolve one promise first (
all
andrace
). - Improves readability.
- Still use callbacks.
- There are a
promises
hell too.
Async/await its a new feature included in ES6+ for handle Promises, we can say that its sugar syntax for handle promises.
The difference it’s pretty simple, now we have to use a function or identify the function that needs to solve an async operation and mark it as an asynchronous function with the async
keyword:
async function someFunction(){
//...
}
/* or */
const someFunction = async () => {
//...
}
This new keyword async
allows you to use another keyword inside the function called await
to await (as it name says) an asynchronous operation response:
async function someFunction(){
const response = await db.find()
}
/* or */
const someFunction = async () => {
const response = await db.find()
}
Our new notation have a couple of thing to notice, the first one is that await
keyword pause the execution flow until the promise is fulfilled, once this happens continues with the execution. The second one is that the error handling it’s not intuitive or included with the notation, to handle errors with async await we have several options.
The first solution its wrap the await call with a try
block and add the catch method. This catch method will catch any error occurred in the try
block:
async function someFunction(){
try{
const response = await db.find()
}catch(error){
console.error(error)
}
}
/* or */
const someFunction = async () => {
try{
const response = await db.find()
}catch(error){
console.error(error)
}
}
The catch block will receive any error thrown inside the try block and you can easily handle the error now.
At the functions invokes there are something we missed along which is the catch
method, this is an avoided feature but super useful.
async function someFunction(){
const response = await db.find()
}
/* or */
const someFunction = async () => {
const response = await db.find()
}
someFunction().catch(error => console.error(error))
Since async/await have not strict way to handle errors you can use a handmade function to solve errors with then/catch and re-use it.
export default function to (promise) {
return promise
.then(data => [null, data])
.catch(err => [ err ])
}
const someFunction = async () => {
const response = await to(db.find())
}
The best thing about this is that you don’t have to write this code in every project you develop, there are libraries like await to js
to re-use.
A useful practice to understand and learn how and when to use async / await it’s refactor then
structure. This is easy but it turns pretty straightforward with this five steps:
- Identify the function that it’s performing an async operation.
// this 👇 is the function that is executing an asynchronous operation.
function getResource(){
DB.find()
.then(response => console.log(response))
.catch(error => console.error(error))
}
- Once we identify the asynchronous function we have to place the
async
keyword before the function keyword or the parentheses in an arrow function.
/* 👉 */async function getResource(){
DB.find()
.then(response => console.log(response))
.catch(error => console.error(error))
}
- The next step now we already marked our function as an async function is identify the callback argument name (in the
then
method) and create a new variable/constant with it.
// step one
async function getResource(){
DB.find()
.then(/* 👉 */response => console.log(response))
.catch(error => console.error(error))
}
// step two
async function getResource(){
const /* 👉 */ response
DB.find()
.then( => console.log(response))
.catch(error => console.error(error))
}
- We already use the
async
keyword in our code, it’s time to useawait
. So let’s add theawait
keyword between the=
and the promise. After that we should assign to our variable/constant the promise
async function getResource(){
/* 👉 */ const response = await DB.find()
.then( => console.log(response))
.catch(error => console.error(error))
}
- The last step is remove just the
then
structure (not the code block) and the fullcatch
(you can handle the error as you want).
async function getResource(){
const response = await DB.find()
console.log(response)
}
This is the easiest way to refactor old promises resolver functions and the best way to get use to implement async/await.
- We have a synchronous looking like code.
- Sequential and stepped solutions for async operations.
- Less code.
- we can just await an async operation without save the result.
- Doesn’t have a consolidated error handling structure.
- Always needs a function to wrap the async operations (we can always wrap the code inside a function or an IIFE).
The history of asynchronous code in javascript it’s interesting and we can easily note the progress of the language with this brand new async/await
operators that allows you to write better code, less code and understandable code. The pure existence of async/await
doesn’t mean that the rest of solutions are bad, it’s just another way to resolve any async operation.