This is the 24th day of my participation in the August More Text Challenge

The built-in util module of Node.js has a promisify() method that converts a callback-based function to a promise-based one. This lets you combine Promise chains and async/await with callback-based apis.

For example, node.js’s FS module needs to use a callback when reading a file:

const fs = require('fs')

fs.readFile('./package.json'.function callback(err, buf) {
  const obj = JSON.parse(buf.toString('utf8'))
  console.log(obj.name) // 'Example' -> package.json Package name
})
Copy the code

We can use util.promisify() to convert the fs.readfile () callback to return a Promise function:

const fs = require('fs')
const util = require('util')
​
// Convert fs.readfile () to a function that takes the same arguments but returns a Promise.
const readFile = util.promisify(fs.readFile)
​
// readFile() can now be used with await!
const buf = await readFile('./package.json')
​
const obj = JSON.parse(buf.toString('utf8'))
console.log(obj.name) // 'Example'
Copy the code

How does Promisify work?

How does util.promisify() work in the background? There is a Polyfill on NPM, and you can read the full implementation here. You can also find the Node.js implementation here, but polyfill is easier to read for ease of understanding.

The key idea behind util.promisify() is to add a callback function to an argument passed in. This callback resolves or rejects the Promise returned by the promisified function.

For ease of understanding, here is a very simplified custom implementation of util.promisify() :

const fs = require('fs')
​
// a simplified implementation of util.promisify(). Do not use this option in a PROD environment.
function promisify(fn) {
  return function() {
    const args = Array.prototype.slice.call(arguments)
    return new Promise((resolve, reject) = > {
      fn.apply(this, [].concat(args).concat([(err, res) = > {
        if(err ! =null) {
          return reject(err)
        }
        resolve(res)
      }]))
    })
  }
}
​
// Convert fs.readfile () to a function that takes the same arguments but returns a Promise.
const readFile = promisify(fs.readFile)
​
// readFile() can now be used with await!
const buf = await readFile('./package.json')
​
const obj = JSON.parse(buf.toString('utf8'))
console.log(obj.name) // 'Example'
Copy the code

So what does that mean? First, util.promisify() adds an extra parameter to the passed parameter, and then calls the original function with those new parameters. This means that the underlying function needs to support this number of parameters. Therefore, if you call myFn() ‘s promisified Function [String, Object] with two type arguments, make sure the original Function supports [String, Object, Function].

So what does this mean? First, util.promisify() adds an extra parameter to the passed parameter, and then calls the original function with those new parameters. This means that the underlying function needs to support this number of parameters. Therefore, if you call promisified Function myFn() with 2 parameters of type [String, Object], make sure that the original Function supports [String, Object, Function].

Second, util.promisify() affects the function context (this).

Lost context

Missing context (this) means that the function call ends with the wrong value. Missing context is a common problem with conversion functions:

class MyClass {
  myCallbackFn(cb) {
    cb(null.this)}}const obj = new MyClass()
const promisified = require('util').promisify(obj.myCallbackFn)
​
const context = await promisified()
console.log(context) // Print undefined instead of MyClass instance!
Copy the code

Remember that this contains any object that has the property of the function when it is called. Therefore, you can preserve context by setting the promisified function as a property of the same object:

class MyClass {
  myCallbackFn(cb) {
    cb(null.this)}}const obj = new MyClass()
// Preserve the context because promisified is a property of OBj
obj.promisified = require('util').promisify(obj.myCallbackFn)
​
const context = await obj.promisified()
console.log(context === obj) // true
Copy the code