It's very easy to get into callback hell in Node if the programmer needs to block the process and chooses not to use Node's sync
methods (or its promise methods).
Since Node is made to deal with lots of streamed data, and since these streams have an unknown size and can take an unknown amount of time to complete, much of Node's functionality is built with callbacks to block parts of the process.
Even just simulating an arbitrary and minimal example of this, it's clear how obnoxiously complicated this can get.
function callFirst(cb) {
const a = 1
cb(a, callSecond)
}
function callSecond(val) {
console.log(val)
}
callFirst(function (value, cb) {
const increment = value + 1
cb(increment)
})
Complexity aside, this pattern provides us with the ability to compose using functions. As is fairly common in Node, some of these functions will be named while others will be anonymous callbacks, which adds another layer of challenge.
Rather than having to rely on possibly nesting callbacks inside of other callbacks, most Node modules have a family of sync
methods that can be used to block execution until they finish.
const data = fs.readFileSync('file.json')
console.log(JSON.parse(data)) // this won't happen until fs.readFileSync() is finished
This can also be done with promise methods that are exposed in most modules. Comparing sync
methods with promise methods, there is not much difference other than just providing more options with syntax. Here's what the above would look like when importing from fs/promises
:
(async function() {
const data = await fs.readFile('file.json')
console.log(JSON.parse(data)) // this won't happen until fs.readFileSync() is finished
})()
Using the fs/promises
version requires the method to be called within an async context. It's a little more verbose, but also might be a little more clear as to what's going on, especially to others who may not be familiar with Node's sync
methods.
It should be noted that both the sync
and the fs/promise
examples would benefit from being wrapped with try
/catch
blocks so that any exceptions don't bubble up and possibly kill the greater process.