Synchronous tasks/programs are the ones in which each instruction is executed step by step, each blocking the processor until it completes execution. Asynchronous on the other hand does not block the processor but execute the tasks in parallel or use a mechanism that makes it look like they work in parallel. To achieve parallelism most programming languages use the concept of threads. The main thread spawns other threads to do some work so that the main program is not blocked.
JavaScript is a synchronous language, it is single threaded. Javascript takes on parallelism with the help of something called event loop. The workings of the event loop are amazing but out of the scope of this post. I would recommend you to watch this talk by Philip Roberts. He explains it in-depth and in a fun way. In simple terms event loop makes it look like different tasks in our program are executing in parallel but that's not the case. Asynchronous code looks different and behaves differently then synchronous one. If you are not careful then you might face issues such as undefined being used rather than the actual value of the async operations.
Why not just go Synchronous?
Well if we just went with Synchronous operations then our programs, UI would be unresponsive during the operation. Imagine if you could not interact with the page each time it made an API call, all the websites would feel laggy and you would get irritated. If your program was dealing with some CPU heavy task other tasks also would have to wait. This would not be a good world to live in.
Input-output operations, Network calls are few examples of async operations.
Dealing with async operations
There are different mechanisms that help you deal with async operations lets get into them.
Callbacks
A callback is a function that will be executed when an async operation is completed. You pass the callback to the async operation and it calls the function when it's done executing.
Let's take an example of reading from a file. To do this we will use the fs module from Nodejs.
const fs = require('fs') // syntax to load a module
fs.readFile('/file-path', (err, data) => {
if (err) console.log(err)
console.log(data)
})
A thing to note about callbacks for most of the operations is their function signature. The convention is that the first argument will be an error object if some error occurred null/undefined otherwise, and the second will be the result of the operation.
Although callback helped us dealing with async operations it created another problem for us, the callback hell.
Callback hell
Consider this situation, you have file1 that has the name of the file2 that you want to read data from. Well, this exact scenario with files is strange but this happens commonly when dealing with API calls when you need to call the second API based on the result of the first one.
const fs = require('fs') // syntax to load a module
fs.readFile('/file1', 'utf8', (err, file2) => {
if (err) console.log(err)
fs.readFile(`${file2}`, (err2, data) => {
if (err) console.log(err2)
console.log(data)
})
})
You have callback inside another callback if you had to do another operation add more callback and so on. The code becomes difficult to look at, debug. As your codebase grows this will lead to bugs, maintenance issues.
Promises
Promises are alternatives to a callback, to use this you need a function that returns a promise object. A promise can either resolve (be successful) or reject (some error occurred), pending: still executing.
This makes the syntax much simpler
readFile('file-path')
.then(res => {})
.catch(err => {})
.then() and .catch() also return a promise so you can have another async operation inside of your then without having to go through callback hell.
The .catch() block helps you handle any error that occurs in the .then() chain.
There are few operations that still don't support promises, as of this writing the fs module does not support promises, to solve this you would rather write a wrapper around this, use fs-extra npm package or if you are using node 8 and above use util.promisify() method.
const util = require('util')
const fs = require('fs')
const readFile = util.promisify(fs.readFile)
readFile('./test.txt', 'utf8')
.then(res => {
console.log('File data ', res)
})
.catch(err => {
console.log(err)
})
Async/ Await
While promises made our life easy async await has made it even easier. This is a different syntax internally async-await uses promises, generators.
The syntax is so clean that an async piece of code looks synchronous. This is amazing as debugging becomes simpler. There are two steps when using this, use the async keyword before a function that is asynchronous, use the await keyword to wait for the async function to return data. You can use the await keyword only inside an async function.
async function getData() {
let data = await readFile("./test.txt", "utf8");
console.log(data);
}
getData();
Error handling in Async / Await
Since Async await are just promises you can just chain a catch block after the async operation, just like with normal promises. If you are not a fan of this you can also use a traditional try-catch block.
If you are starting with node I hope this article helps, in the future article we will take a look at how much asynchronous code helps when dealing with API calls. If you are facing issues with the result of asynchronous operations do check how you have handled them.
Do share the post if you liked it.
Cover Photo by HΓ©ctor J. Rivas on Unsplash
Top comments (17)
This is a good explanation of the various ways we can work with JavaScript asynchronously.
I would recommend you take a look at your introduction again though. JavaScript may be able to run asynchronous tasks concurrently (seemingly at the same time) but it is not truly parallel. The answers in this Stack Overflow question have some good explanations and diagrams for what that really means.
I was really confused when I started the second paragraph and you said:
I think you meant:
It might help to clear that up!
Thanks again for writing this.
Actually, JavaScript is basically a synchronous, single-threaded language with a few extension to allow events to interrupt this single thread and to run asynchronous code either inside a web worker or the GPU.
Hmm, OK, it's not the language that is specifically asynchronous, just the two most popular platforms (web and Node.js) on which the language runs. But that doesn't mean I'd describe JS as a "synchronous" language.
Events don't interrupt the thread, both the web and Node.js run JavaScript code via an event loop. Both the talk by Philip Roberts linked in the article and this talk by Erin Zimmer do a great job of explaining how that works. There are various asynchronous pieces to JavaScript depending on the platform it runs on and they all take their time on the event loop.
Web Workers are something interesting though. They do actually give browser based JavaScript more threads to run on across more CPUs if you have them available. They are an extension that actually makes JavaScript run in parallel, not just concurrently. There appears to be experimental support for multithreading in Node.js since version 10.5 too. It's exciting times for multithreading JavaScript!
We should clarify which version we are talking about. If we are talking about the first implementation of JavaScript, this one was synchronous and events did interrupt the main thread.
That doesn't mean that the language itself is necessarily synchronous, but those async implementations are rather referred to as ECMAscript instead of JavaScript.
I think you may be hard pressed to find anyone on this site, or even the web, who writes a post about JavaScript and means the version from Netscape 2 (without explicitly saying so).
Still, to the day the main thread runs completely synchronous unless you use events, Promises or async/await either directly or indirectly.
Sure, if you only write synchronous code it will only run synchronously. Β―_(γ)_/Β―
If your program was dealing with some CPU heavy task other tasks also would have to wait.
This is still true for asynchronous code. With your async code, other tasks would have to wait if your cpu core is busy.
Yeah right.
Nice post!
Omg! Thank you :D
For "You can use the async keyword only inside an async function." I think you wanted to say await.
Yes, thank you :)
Thanks, good explain.
Thank you :)
Interesting post! Thanks!
Great article, Async / Await is one of the best features in JavaScript added, you can check here more about Async and Await codespot.org/async-javascript-part...