NodeJS is very popular programming language when it comes to rapid prototyping of backend and can easily scale up to thousands of user without having to change code. When nodeJS came to picture due its asynchronous operations it was using callback style pattern to transfer back to calling code.
Request/Response paradigm
As we know our request/response paradigm requires synchronous output most of the time and those request often include calling multiple async operation where each operation is linked with successful execution of previous operation. This led to problem called “Callback Hell” which made code difficult to understand for developer especially due to lots of nesting.
Explicit call to callback and Error propagation
With the callback approach there should be explicit call to callback function to execute next function, it often creates dependency on developer to write and transfer Error (if any) to the next function. which increases the effort on developer part for crucial error propagation and handling and also increases verbosity of the code.
function someprocess(callback) {
fs.readFile('./path/to/file', function (err, filedata) {
if (err) return callback(err);
// process filedata here
db.update(processedData, function (err, result) {
if (err) return callback(err);
// check in result how many rows affected
fs.writeFile('./path/to/file', someMoreData, function () {
// ... do another processing
callback();
})
})
});
}
Introduction of Promise
Promise was the new way of handling async operation in NodeJS. It eliminated the need to explicitly call and/or pass on the next calling function. Promise style made it possible to use normal “return” and “throw” keywords which made it more developer friendly and easy to understand and made it seem like synchronous style. it avoided nesting but introduced chainable function or promises.
somePromise.then(function (data) {
// process data
return anotherData;
}).then(function (anotherData) {
// process another data
if (someErrorHappend) {
throw "Some error happened!"
} else {
// transform data
return transformedData;
}
}).catch(function (error) {
throw error;
});
While promise has its advantage over callbacks but there were more areas that it needed to be improved. Basic area was to reduce verbosity more and a way to tackle chaining to more simplified and make it more imperative.
async/await keyword (sugar syntax)
Async and await are the two keywords later introduced in JS to abstract away the promise creation and resolution from the developer. These two keyword are used together to create a illusion of procedural language that we are used to and allows us to write code without any or much different from procedural language. Addition of these allowed developer to catch error with try/catch/finally blocks which allowed developer to least verbose code involving async operation.
async function someprocess() {
try {
const data = await somePromiseReturningFn();
const anotherData = await somePromiseReturningFn1(data);
const someOtherData = await somePromiseReturningFn2(anotherData);
return someOtherData;
} catch (error) {
throw error;
}
}
After introduction of async/await there are many nodeJS built-in modules which does not support promises and implement old callback pattern for IO operation. As NodeJS was being used on production ready server by many companies which had used callback style pattern, so to avoid major breaking changes and to maintain backward compatibility core node modules were kept unchanged.
Move from callback to promise
Callback was widely used when there was no Promises and it was only way to handle asynchronous tasks. After introduction of Promise new development should use Promise as it is clean to implement and less verbose and easy to understand the flow of code.
To convert callback to Promise, we can use multiple third party libraries such as bluebird, pify, pinkie-promise, etc. NodeJS also provides utility method called “promisify” to convert node style callback to promise. It is probably the best solution to go for when you don’t want to rely on third party library.
const { promisify } = require('util');
async function someprocess() {
try {
const proimisifiedFuntion = promisify(someCallbackStyleFunction);
const data = await proimisifiedFuntion(...normalArgs);
return data;
} catch (error) {
throw error;
}
}