Engineer bro!
Mon Feb 21 2022 (1 year ago)
Engineer by mistake!
I am a software engineer by passion
The util.promisify() function takes a function following the common error-first callback style, i.e. taking a (err, value) => ... callback as the last argument, and returns a version that returns promises. There is a native implementation present in here, but you might get this question in an interview, so we should be able to answer that question.
This question requires you to have some basic understanding of key terms of JavaScript which I am going to explain to you below.
JavaScript, often abbreviated JS, is a programming language that is one of the core technologies of the World Wide Web, alongside HTML and CSS. Over 97% of websites use JavaScript on the client-side for web page behaviour, often incorporating third-party libraries.
Node.js is an open-source, cross-platform, back-end JavaScript runtime environment that runs on the V8 engine and executes JavaScript code outside a web browser.
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
In computer programming, a callback, also known as a "call-after" function, is any reference to executable code that is passed as an argument to other code; that other code is expected to call back the code at a given time.
In simple terms -> A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
Error-First Callback in Node. js is a function that either returns an error object or any successful data returned by the function. The first argument in the function is reserved for the error object. If any error has occurred during the execution of the function, it will be returned by the first argument.
Let's take a look at fs.readFile of NodeJs
import { readFile } from "fs";
readFile("/etc/passwd", { encoding: "utf-8" }, (err, data) => {
if (err) throw err;
console.log(data);
});
When you inspect syntax of readFile, then you'll get to see that fs.readFile is accepting three arguments.
File path
Options
Callback Function
Here, we only have to deal with the third argument. As you can see callback function has the following syntax -> (err, data)
. The first argument is the Error
object specifying the error that occurred while reading the file. It is an easy way to identify the error that occurred by just checking the first argument because sometimes we can have more than two arguments.
The problem with callbacks is the callback hell.
https://www.quora.com/What-is-callback-hell
After reading the above-mentioned articles, you might have guessed that we can solve the problem caused by callback hell with promises (async/await)
.
But, the problem here is that not all APIs of NodeJs support Promise or in simple terms, NodeJs APIs do not return Promise
.
const fs = require("fs");
fs.readFile("./bid.js")
.then((data) => console.log(data))
.catch((err) => console.log(err));
// OR
async function test(){
const data = await fs.readFile("./bid.js")
// do processing
}
test()
When you try the above code, then you might get an error like below
fs.js:169
throw new ERR_INVALID_CALLBACK(cb);
^
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received undefined
The error is coming because we are not passing the callback function required by fs.readFile
. But, here we are trying to get rid of the callback hell and migrate to promise style async/await
.
For now, can create a function that will take all the arguments required by fs.readFile
and returns a promise that will resolve to the file content.
const fs = require("fs");
function readFile(path, options = {}) {
return new Promise((resolve, reject) => {
fs.readFile(path, options, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
async function test() {
const data = await readFile("./bid.js");
// do processing
}
test();
Now, you can see that the above function is working. We have created another function readFile
that will accept path
and options
as an argument and returns a Promise.
Although we have solved the problem but do we have to create another function for all the APIs which I need to use. If we started to create another function for every API, then our codebase will increase with only redundant codes.
To solve this problem, NodeJs has a utility function, util.promisify() that can take an error-first callback function as an argument and returns a function that can return a promise. So, we can use async/await
to solve the problem of callback-hell. Let's see this in the next section.
Takes a function following the common error-first callback style, i.e. taking a (err, value) => ...
callback as the last argument, and returns a version that returns promises.
const util = require("util");
const fs = require("fs");
const readFile = util.promisify(fs.readFile);
const readdir = util.promisify(fs.readdir);
async function test() {
const data = await readFile("./bid.js");
const dirs = await readdir(__dirname);
// do processing
}
test();
As you can see the problem is resolved with just a few lines of code.
Now you have the idea of basics, the problem with callbacks, callback hell, util.promisify() etc.
Now, we can focus on the main Aim of the article. Suppose an interviewer asked us to build our own util.promisify()` function, then we should be able to answer this question.
Let's see how can we build our own util.promisify() function.
Before we start building the util.promisify() function we should know the architecture of util.promisify() function, i.e what it takes as the argument and what it returns.
util.promisify() function takes an error-first callback function as the argument
util.promisify() function returns an function that will eventually return a Promise.
Now, we have an idea of what util.promisify() takes as an argument and what It returns.
Let's start creating util.promisify()
So, the basic requirement of util.promisify() is that it should take a function as an argument and returns a function that will, in turn, return a Promise.
/**
* Convert error-first callback function into Promisified function
* @param {Object} fn callbback function
* @returns {Object} a function
*/
function customPromisify(fn) {
return function () {
return new Promise((resolve, reject) => {
});
};
}
Now, let's talk about the arguments of the function which needs to be converted, for example fs.readFile()
.
fs.readFile()
accepts three arguments, path, options and callback. There might be another function that takes more than three arguments and that need to be converted.
So, our customPromisify
should be able to handle that case too. We can achieve this using arguments.
/**
* Convert error-first callback function into Promisified function
* @param {Object} fn callbback function
* @returns {Object} a function
*/
function customPromisify(fn) {
return function () {
return new Promise((resolve, reject) => {
// arguments - it will pass down all the arguments
fn(...arguments, (err, ...data) => {
if (err) reject(err);
else resolve(...data);
});
});
};
}
const readFile = customPromisify(fs.readFile);
async function test() {
const data = await readFile("./bid.js",{ encoding: "utf-8" });
// do processing
}
test();
When you run the above code, then you'll see that everything is working fine.
Congratulations 🎉, we have created our own customPromisify
function.
We have created our own util.promisify() function. This is able to handle the case of the variable number of arguments and tested it properly.
Thank You for reading the article till the end.
© 2021 dsabyte. All rights reserved