[Node.js] Why using sync versions of async functions is bad.

[Node.js] Why using sync versions of async functions is bad.

ยท

5 min read

Hey!

My name is Gaurang and I've been working with Node.js for the past 5 years. This article is part of the "My Guide to Getting through the Maze of Callbacks and Promises" series. It's a 3-part series where I talk about writing asynchronous code with Node.js.

The articles are intermediate-level and it is expected that the reader already has basic familiarity with Node.js syntax and the constructs related to asynchronous programming in it. Such as callbacks, promises, and async-await.


Sync of Async what?

Before I go ahead and beef about synchronous functions, I guess it would be customary for me to briefly describe synchronous as well as asynchronous functions.

Many Node.js packages provide synchronous alternatives to their asynchronous functions. For example, the fs package has fs.readFile (which works asynchronously) and fs.readFileSync (which works synchronously).

The working of fs.readFileSync is similar to traditional functions. Your program calls the method and waits for the file to be read. Once the file is completely read, it returns the contents. The program then resumes execution of the rest of the code.

Now reading a file is a slow I/O operation that could take anywhere between a few hundred nanoseconds to a few seconds. So when our program calls the asynchronous fs.readFile method, it does not wait for the method to read the entire file and return the contents. Our program moves ahead with the execution of the rest of the code immediately; while fs.readFile continues with the reading task in parallel.

If there's any code you want to execute after the file contents have been read, you need to wrap the code in a function and pass that function to fs.readFile. fs.readFile will call that function as soon as the file contents have been read. This way of writing code is an engineering choice made by the creators of JavaScript/Node.js that allows it to be extremely fast despite being single-threaded.

Here's a sample script demonstrating the difference between synchronous and asynchronous programming in JavaScript.

What's wrong with using sync versions of async functions?

Asynchronous execution is such a vital part of JavaScript that you'll find asynchronous functions everywhere. And you cannot write effective asynchronous code if you're thinking with the traditional synchronous approach.

The advent of async-await has made writing code in JavaScript fairly straightforward. And so, most people learning JavaScript today do not know what a pain it used to be about 6-7 years back. Callback-hell has become a thing of the past.

However, I often come across codebases where callbacks, promises, and async-await have been mixed together. Turning the code into a hotchpotch. I sympathize with the people that have to work with such codebases. Understanding callbacks and promises is difficult. Writing code with callbacks is difficult. Understanding the difference between synchronous and asynchronous functions is difficult.

Sometimes I see people retreating to synchronous versions of library methods because they're more convenient. E.g. fs.readFileSync, fs.writeFileSync, fs.copyFileSync, child_process.execSync, etc. And that in my opinion is one of the worst things to do to your project. The main selling point of Node.js is asynchronous input/output. By using synchronous versions of I/O-related methods, you're not just missing out on the main benefit of JavaScript that it has been modelled for. You're also making it perform much worse than if the application was written in any other language.

Node.js is single-threaded. And therefore, just one long-running synchronous (blocking) task is enough to degrade the performance of the entire application. Asynchronous functions are designed to be non-blocking and to avoid delays. Using synchronous versions of asynchronous functions may seem like an easy solution at first, but it is the devil in disguise. It can cause delays all over the application; leading to frustration for developers and users alike.

Of course, if you're writing a small independent one-time script, it may not matter much. But in large production-level applications, this is going to cause unnecessary bottlenecks.

Here's a short demo of how a synchronous method call affects the rest of the application.

This script starts a barebones Node.js server that has three APIs.

  • GET /quick-api

  • GET /slow-sync-api

  • GET /slow-async-api

The quick-api API is supposed to return a response immediately. But, if you call quick-api after slow-sync-api, it does not return until slow-sync-api returns. This is because Node.js is single-threaded. And slow-sync-api calls a synchronous method that does not let the main thread do anything else till its execution is complete. It blocks all other requests. However, if you call quick-api after slow-async-api, the quick-api works as expected. This is because slow-async-api does not block the main thread till its execution is complete. The work of the asynchronous function is deferred to a worker thread and the main thread waits for the execution to complete. Meanwhile, the main thread is free to fulfil other requests.

Conclusion

In conclusion, using the synchronous versions of asynchronous functions is a bad practice. It can lead to unexpected behavior and performance issues. If you need to use an asynchronous function, it's best to use the asynchronous version. This will help you avoid any potential problems and ensure that your code is as efficient as possible.

I've put a lot of work into this article. And I hope that it has been helpful. If you have any questions, please feel free to leave a comment below. I'd really appreciate it if you could like and share the article! ๐Ÿ˜Š

Did you find this article valuable?

Support Gaurang Pansare by becoming a sponsor. Any amount is appreciated!

ย