The asynchronous nature of Javascript
June 28, 2018
It all started when I first learned Node.js for back-end development. I started with some simple snippet like the following:
var http = require('http');
//create a server object:
http.createServer(function (req, res) {
res.write('Hello World!'); //write a response to the client
res.end(); //end the response
}).listen(8080); //the server object listens on port 8080
This looks simple enough albeit the seemingly slightly odd syntax, all it does is creating a HTTP server that listens to a server port and gives a response when it receives a request. However, this odd syntax has something to do with the topic today: asynchronous function. Actually there’s more to it, but I write about that in another post.
Synchronous and Asynchronous functions
What do these two terms mean and what’s the difference between them? I found some excellent answers for this on Stack Overflow here and here, they are both good reads if you are interested in more details. But TLDR, synchronous function is when all of the code executes in sequential order, i.e. after a function is called, any code after the call has to wait for the function to return a result before they can continue to execute. On the other hand, in asynchronous function, code after a function executes immediately after that function is called, even when a result is not returned yet.
You might be wondering: What if I want to do something with the result? Where should I put such code? That’s when a callback function comes in handy. You would see this term a lot if you are dealing with node.js. A callback function only executes when the function that calls it returns a result. Let’s see the below example:
var http = require('http');
//create a server object:
http.createServer(function (req, res) {
res.write('Hello World!'); //write a response to the client
res.end(); //end the response
}).listen(8080); //the server object listens on port 8080
console.log("This executes immediately after the last function is called.");
The anonymous function in the createServer is a callback function. It only executes when a http request comes in at port 8080. The log function will execute immediately after you initiate this nodejs file.
The asynchronous nature is quite useful, especially when it comes to web programming. When a request comes in to the server, for exmaple a request to query some information from a database, the server would pass the query to the database, then it’s ready to handle the next request. After the query finishes, the server could then return the result to the client via a callback function.
However, the asynchronous nature also means that you have to program in a specific way, and the code can get a bit ugly sometimes. For example the following code which query a database and use the result:
const session = require('express-session');
const app = express();
const mysql = require ('mysql');
const db = mysql.createConnection({
/* some database initialisation */
})
db.connect((err)=>{if(err) throw err;});
app.get('/someroute',(req, res)=>{
db.query('somequery', err, result) => {
if(err) throw err;
/* some response to the client with the 'result' */
}
})
Suppose you have to do more than one query to the database with one request, and return all the results to the client in one response. The simple way will probably look like this:
app.get('/someroute',(req, res)=>{
db.query('somequery1', (err, result1) => {
if(err) throw err;
})
db.query('somequery2', (err, result2) => {
if(err) throw err;
})
db.query('somequery3', (err, result3) => {
if(err) throw err;
})
/* response to the client with result 1,2 and 3 */
})
This look clean and neat. But, as you may already see, this is wrong, and in more than one way. Firstly, the response is not in the callback function, meaning that it wouldn’t know about the results. Moreover, the 3 queries are done independently, so their results will be returned independently and cannot be sent back to the client in one response. So what’s the correct way to do this?
app.get('/someroute',(req, res)=>{
db.query('somequery1', (err, result1) => {
if(err) throw err;
db.query('somequery2', (err, result2) => {
if(err) throw err;
db.query('somequery3', (err, result3) => {
if(err) throw err;
/* response to the client with result 1,2 and 3*/
})
})
})
})
In the above code, the three callback functions are nested. The second query function will only execute when the callback function in the first query returns a result, the third query will only execute when the second query returns a result. Finally, when the third query returns a result, all the results can be returned to the client in one response.
As everything else in the world, this approach is a double-edge sword. If done excessively, it becomes what is known as the callback hell, so use it wisely!