WittCode💻

Don't Use Synchronous Code If You Don't Have To

By

Learn how synchronous code can cause performance issues in a Node application and why using asynchronous code is better.

Table of Contents 📖

Synchronous Code

Node is single threaded. The single thread that executes code is called the event loop. In Node programs, synchronous code blocks the event loop. When the event loop is blocked, the program is blocked. For example, consider the following code:

app.get('/synchronous', (req, res) => {
  for (let i = 0; i < 100; i++) {
    for (let j = 0; j < 10000; j++) {
      console.log('sync', i, j);
    }
  }
  console.log('Done synchronous task');
  return res.sendStatus(200);
});

Making a call to this route will stall the entire application until this quadratic operation is complete. This means Node won't be able to handle any other requests. The same is true with using the fs module to read a file synchronously.

app.get('/synchronous', (req, res) => {
  const data = fs.readFileSync('./largeFile.txt');
  return res.sendStatus(200);
});

The readFileSync method performs the file reading operation directly on the main thread, blocking the Node event loop until the operation is complete. During this time, no other tasks can execute, which can severely degrade performance in an application that serves multiple requests or handles multiple tasks.

Asynchronous Code

On the other hand, Node offloads asynchronous tasks to the system. Node consists of several external dependencies to function properly. One of these is Libuv, a C library that focuses on asynchronous I/O. Simply put, Libuv handles Node's asynchronous operations. This means that if we use the asynchronous fs module methods, the main thread will not be blocked.

INFO: Libuv was actually primarily developed for use by Node but it is used by other languages too.

app.get('/asynchronous', async (req, res) => {
  const data = await fs.promises.readFile('./largeFile.txt');
  return res.send(data);
});

The asynchronous readFile method offloads the file reading operation to libuv, which uses the thread pool to handle it. While the file is being read, the Node event loop remains free to handle other tasks, so the main thread is not blocked. A Promise is used to notify the JavaScript code when the operation is complete, allowing the main thread to stay responsive.