This tutorial is a part of the Learn everything about Javascript in one course.
Simple defintion of synchronous and asynchronous operations
synchronous operation | asynchronous operation | |
---|---|---|
Definition | Operation where input data is present and ready to be used | Operation where input data is not present |
Where to get input data | No need, it's present | Network call, read from drive |
Example | perform x = 1 + 2 |
get user data from server, read video file from drive (I/O tasks in general) |
Defintion of synchronous and asynchronous functions
synchronous function | asynchronous function | |
---|---|---|
Definition | is function which handles ONLY synchronous operations | is function which handles asynchronous operations |
Operation can be handled | synchronous | asynchronous and synchronous |
Example | function to perform x = 1 + 2 |
function to: get user data from server, read video file from drive |
Sequence of code execution | line after line as seen in program | vary depend on when synchronous operation finishes absolutely not line after line as seen in program |
How to handle returned data (even empty returned data) | handle it in following lines of code | use callback , Promise or async/await |
// synchronous function
// next line of code executed after previous one finished
console.log('1st line')
let name = 'Tony Stark'
function greet(name) {
console.log('Hello ' + name + '!')
}
greet(name)
console.log('last line')
// 1st line
// Hello Tony Stark!
// last line
// asynchronous function
console.log('1st line') // 1st line
const username = 'username'
const password = 'password'
function login(username, password) {
setTimeout(() => {
console.log('login success with credentials:', username, password)
}, 1500) // we emulate login result returns after 1.5 second
}
login(username, password)
console.log('last line')
// 1st line
// last line (does not wait for function login() to finish)
// login success with credentials: username password
Callback function, call me when you are done
A callback (callback function):
- is a function which is called after asynchronous operation is done.
- is where you can implement post asynchronous operation logic.
- Call me back when you are done, hence "callback".
- is passed in as an argument to function which does asynchronous operation.
- "callback" is extremely popular in Javascript.
// use callback functions to handle post asynchronous operation
// (after login)
console.log('1st line')
const username = 'myusername'
const password = 'myPassword'
const successCallback = () => { // a callback function
console.log('login success, I am doing the next thing')
}
const failureCallback = () => { // a callback function
console.log('login failed, reporting failure')
}
function login(username, password, successCb, failureCb) {
setTimeout(() => {
console.log('credentials:', username, password)
let ok = true // we assumme login success
if(ok) {
successCb() // call success cb when login success
} else {
failureCb() // call failure cb if login failed
}
}, 1500) // we emulate login result returns after 1.5 second
}
login(username, password, successCallback, failureCallback)
console.log('last line')
// 1st line
// last line
// credentials: myusername myPassword
// login success, I am doing the next thing
You can define a callback function right when passing it in to a function. Two examples below produce the same result.
Separate cb declaration | Shorthand cb definition | |
---|---|---|
Declare and call | const cb = () => { console.log('running callback') } login(cb) |
login(() => { console.log('running callback' }) |
Callback hell
: happens when you nest many callback function together. For example: call func1
after login()
is done, then call func2
after func1
is done, ... It's hell
because it's harder to read.
// callback hell
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult)
}, failureCallback)
}, failureCallback)
}, failureCallback)
To make asynchronous logic look prettier and easier to understand, Javascript offers Promise
and async/await
. We will learn about them next.
Javascript Promise, flatten the callback hell
Declare const promise = new Promise((resolve, reject) => { // do something })
Usage promise.then((resolveVal) => { // do something }).catch((error) => { // handle error })
// Promise
console.log('first line') // (I)
const fetchUserInfo = new Promise((resolve, reject) => {
setTimeout(() => resolve({ name: 'Tony' }) , 1000)
setTimeout(() => reject('nah') , 2000)
})
fetchUserInfo
.then(resolveVal => console.log(resolveVal)) // (II)
.catch(err => console.log(err))
console.log('beetween promises') // (III)
// Promise is chainable, you can have as many "then" as wanted
fetchUserInfo
.then(resolveVal => 'Hello ' + resolveVal.name + '!') // value returned by this "then"
.then(greet => console.log(greet)) // (IV), is input of next "then"
.then() // you can chain as many "then"
.then() // as needed
.then() // even with empty ones
.then(() => fetchUserInfo) // you can aslo return a promise
.then(val => console.log(val)) // (V), this is how we "flatten" the callback hell
.catch(err => console.log(err))
console.log('last line') // (VI)
// first line (I)
// beetween promises (III)
// last line (VI)
// { name: 'Tony' } (II)
// Hello Tony! (IV)
// { name: 'Tony' } (V)
Promise.all(), wait for all given promises to finish
The Promise.all()
method returns a single Promise that fulfills when all given promises have been fulfilled. All promises run in parallel.
Syntax Promise.all([promise1, promise2, ...]).then(([value1, value2, ...]) => { // do something })
// Promise.all() run all promise in parallel and
// get the result when all of them finish
console.time('top_to_bottom')
console.time('start_to_finish_all_promise')
console.time('start_to_finish_promise_2')
console.time('start_to_finish_promise_3')
const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve('foo')
console.timeEnd('start_to_finish_promise_2')
}, 900)
})
const promise3 = new Promise((resolve) => {
setTimeout(() => {
resolve('bar')
console.timeEnd('start_to_finish_promise_3')
}, 300)
})
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
console.timeEnd('start_to_finish_all_promise')
});
console.timeEnd('top_to_bottom')
// top_to_bottom: 0.594ms
// start_to_finish_promise_3: 305.773ms
// start_to_finish_promise_2: 906.264ms
// [ 3, 'foo', 'bar' ]
// start_to_finish_all_promise: 908.998ms
async/await syntax to improve code readability of async logic
async
function, syntax async function name() {...}
or async () => {...}
:
- defines an asynchronous function.
- returns an implicit Promise.
- syntax and structure of code using async functions looks like synchronous functions.
await
operator, syntax value = await aPromise
or value = await asyncFuntion()
:
- is used to wait for a Promise.
- can only be used inside an async function.
Comparision of callback
, Promise
and async/await
:
callback | Promise | async / await | |
---|---|---|---|
Purpose: to handle returned data from asynchronous operation | ✅ | ✅ | ✅ |
Can be used to create asynchronous operation | ✅ | ✅ | ✅ |
Where logic to handle returned data from asynchronous operation lives | within callback function | within then(...) part |
within async function |
How to wait for multiple asynchronous operations sequencially | nested callback (hell) | chained "then" .then(..).then(..) |
use multiple await |
How to wait for multiple asynchronous operations in parallel | ❌ (extra complicated logic) | use Promise.all() |
use Promise.all() |
How nested async code look like | (callback) hell | "flatten", but a lot of .then(...) |
like synchronous code |
Code readability | worst | somewhat ok | easiest to read |
Suggested usage | minimal | minimal | as much as possible |
// async/await syntax to improve code readability of async logic
// 3 different styles of handling returned data from
// fetchUser asynchronous operation
console.time('top_to_bottom')
console.time('top_to_callback')
console.time('top_to_promise')
console.time('top_to_async_await')
// callback style
const handleUserDataCallback = (val) => {
console.log('handle user data, callback style:', val)
console.timeEnd('top_to_callback')
}
function fetchUserCallbackStyle(callback) {
setTimeout(() => {
callback({ user: 'Superman', superpower: 'fly' })
}, 1000)
}
fetchUserCallbackStyle(handleUserDataCallback)
// promise style
const fetchUserPromiseStyle = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ user: 'Aquaman', superpower: 'dive' })
}, 2000)
})
fetchUserPromiseStyle
.then(val => {
console.log('handle user data, promise style:', val)
console.timeEnd('top_to_promise')
})
.catch(err => console.log(err))
// async/await style
const fetchUserAsyncFunctionStyle = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ user: 'Deadpool', superpower: 'indestructible' })
}, 3000)
})
}
const handleUserDataAsyncFunctionStyle = async () => {
const val = await fetchUserAsyncFunctionStyle() // also see the difference of
const val2 = await fetchUserPromiseStyle // calling promise and function here
console.log('handle user data, async function style:', val)
console.log('handle user data, async function style:', val2)
console.timeEnd('top_to_async_await')
}
handleUserDataAsyncFunctionStyle()
console.timeEnd('top_to_bottom')
// top_to_bottom: 0.759ms
// handle user data, callback style: { user: 'Superman', superpower: 'fly' }
// top_to_callback: 1006.114ms
// handle user data, promise style: { user: 'Aquaman', superpower: 'dive' }
// top_to_promise: 2006.171ms
// handle user data, async function style: { user: 'Deadpool', superpower: 'indestructible' }
// handle user data, async function style: { user: 'Aquaman', superpower: 'dive' }
// top_to_async_await: 3002.161ms
Summary
callback
,Promise
andawait/async
are used for handling post-asynchronous operation logic.- Although
await/async
is in favor to use, it's built uponcallback
andPromise
. So it's extremely important to know them all, especially the comparision table.