JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.
In this article, we’ll look at the best practices for writing asynchronous code with Javascript.
Use Promises
Promises are very useful for writing asynchronous code in JavaScript since we can chain multiple pieces of asynchronous code together without nesting multiple asynchronous callbacks.
We can’t nest too many asynchronous callbacks deeply because it’s very hard to read and follow. Therefore, it’s very easy to create bugs and making changes will be very slow.
With promises, we can chain multiple promises together and make writing asynchronous code in a series very easy.
For instance, we can write the following code to chain multiple promises together as follows:
Promise.resolve(1)
.then(val => Promise.resolve(val * 2))
.then(val => Promise.resolve(val * 3))
In the code above, we called Promise.resolve
, which is asynchronous and returns a promise. Each Promise.resolve
call is queued at the end of the event loop and runs when the JavaScript main execution thread is free of any queued tasks.
The chaining we did above is much cleaner than we would have with nested callbacks. The val
just takes the value we have from the promise that’s resolved before.
As we can see, we can also take the value from the previously resolved promise and do something with it. This is something that’s hard to do with nested callbacks as we have to do 3 levels of nesting to do the same thing.
To catch errors that occurs when any promise is being executed, we can call catch
with a callback as follows:
Promise.resolve(1)
.then(val => Promise.resolve(val * 2))
.then(val => Promise.reject('fail'))
.catch(err => console.log(err))
In the code above, we called Promise.reject
, which will cause the promise chain to fail. Promise.reject
takes a reason for the failure and the reason will be available in the err
object in the catch
callback.
So, we’ll see 'fail'
logged from the promise callback.
The promise stops running when the first promise that fails is run, so we can only have one catch
call in the promise chain.
The error handling is also something that’s not available with nested async callbacks unless the code that takes the callback explicitly returns an error as a parameter in the callback.
Also, we have to do more nesting to handle errors in nested async callbacks, which means that it makes reading the code even harder and create more bugs.
To make promises even shorter, we can use the async
and await
syntax for chaining promises.
For instance, we can write the following code to use async
and await
so that we can remove the then
and callbacks but do the same thing as before:
(async () => {
const val1 = await Promise.resolve(1);
const val2 = await Promise.resolve(val1 * 2);
const val3 = await Promise.resolve(val2 * 3);
})()
This is cleaner than using then
and callbacks since we now have no callbacks in our code and it’s only 3 lines. Also, the readability of the code isn’t made worse by using it.
val1
, val2
, and val3
have the same values the parameter in the then
callbacks, which are the resolved values.
To catch errors when a promise in the promise chain fails, we can just use try...catch
as we do with synchronous code.
For instance, we can write the following code to catch promises with async
and await
:
(async () => {
try {
const val1 = await Promise.resolve(1);
const val2 = await Promise.resolve(val1 * 2);
await Promise.reject('fail')
} catch (err) {
console.log(err)
}
})()
In the code above, we wrapped a try...catch
block around our promise code so that we can catch errors with our promise chain. It stops running when the promise fails so one catch block will do everything.
Photo by Hello I’m Nik ? on Unsplash
Run Unrelated Promises in Parallel with Promise.all
We should use Promise.all
to run promises that are unrelated in parallel. This way, we won’t have to wait for one promise to resolve before running another unnecessarily, thereby speeding up our code.
Promise.all
takes an array of promise and returns a promise that has the array of resolved values.
For instance, we can write the following code to use Promise.all
:
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
])
.then(([val1, val2, val3]) => {
console.log(val1, val2, val3);
})
In the code above, we have 3 promises with Promise.resolve
in an array. We then use that to call Promise.all
to resolve them in parallel. Then we get the resolved values in the callback and log them.
With the async
and await
syntax, we can write:
(async () => {
const [val1, val2, val3] = await Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
])
console.log(val1, val2, val3);
})();
since Promise.all
returns a promise.
Conclusion
When writing async code in JavaScript, we should be using promises. This way, they can be chained easily and we can also run them in parallel with Promise.all
.
Removing nested increases readability by a lot and so mistakes are less likely to be made. Also, error handling is standard since we can call catch
with promises to catch errors.
The async
and await
syntax takes chaining promises shorter. Also, we can catch errors with it with try...catch
.