前言

本期的课程主要学习面试高频考点 promisify 的原理和实现。

promisify

promisify 是Node.js 内置的 util 模块中的一个函数,该方法将基于回调的函数转换为基于 Promise 的函数。这使您可以将 Promise 链和 async/await 与基于回调的 API 结合使用。

常规的回调方式

例如使用node的fs模块读取文件时:

const fs = require('fs')

fs.readFile('./package.json', function callback(err, buf) {
  const obj = JSON.parse(buf.toString('utf8'))
  console.log(obj.name)
})

通常只有一个回调函数的时候还好,当回调函数中又包含了回调函数的时候,你的代码将拥有无数的花括号,也就是所谓的回调地狱,光是听名字就很可怕了。

util.promisify() 的出现将很好的解决这些问题。

使用util.promisify() 将 fs.readFile() 的回调函数转换为返回 Promise 函数:

const fs = require('fs')
const util = require('util')
// 将 fs.readFile() 转换为一个接受相同参数但返回 Promise 的函数。
const readFile = util.promisify(fs.readFile)
// 现在可以将 readFile() 与 await 一起使用!
const buf = await readFile('./package.json')
const obj = JSON.parse(buf.toString('utf8'))
console.log(obj.name)

这段代码使用了Node.js中的util.promisify()方法将fs.readFile()方法转换为一个返回Promise对象的函数,以便可以使用async/await语法来处理异步操作。

具体来说,这段代码首先引入了Node.js中的fs和util模块,然后使用util.promisify()方法将fs.readFile()方法转换为一个返回Promise对象的函数readFile()。接着,使用await关键字调用readFile()方法读取指定路径下的package.json文件,并将读取到的数据转换为一个JavaScript对象。最后,将该对象的name属性输出到控制台。

这种方式可以使代码更加简洁和易读,避免了回调函数嵌套的问题,提高了代码的可维护性和可读性。

promisify 的工作原理

promisify 的简单实现

function promisify(original){
    function fn(...args){
        return new Promise((resolve, reject) => {
            args.push((err, ...values) => {
                if(err){
                    return reject(err);
                }
                resolve(values);
            });
            // original.apply(this, args);
            Reflect.apply(original, this, args);
        });
    }
    return fn;
}

const fs = require('fs')

// 将 fs.readFile() 转换为一个接受相同参数但返回 Promise 的函数。
const readFile = promisify(fs.readFile)

// 现在可以将 readFile() 与 await 一起使用!
const buf = await readFile('./package.json')

const obj = JSON.parse(buf.toString('utf8'))
console.log(obj.name) // 'Example'

promisify 方法接收一个回调函数作为参数,返回一个新的函数,这个函数将多绑定两个参数,一个是this,另一个是Promise 对象的结果,成功返回回调函数执行的结果,失败返回错误信息。

这段代码定义了一个自定义的promisify()函数,用于将一个原始的Node.js回调函数转换为一个返回Promise对象的函数。

具体来说,该函数接受一个原始的Node.js回调函数作为参数,返回一个新的函数fn,该函数接受与原始函数相同的参数,并返回一个Promise对象。在fn函数内部,将原始函数的参数列表中添加一个回调函数,该回调函数在原始函数执行完成后被调用。如果原始函数执行过程中出现错误,则将Promise对象的状态设置为rejected,并将错误信息传递给Promise对象的catch()方法。如果原始函数执行成功,则将Promise对象的状态设置为resolved,并将原始函数的返回值传递给Promise对象的then()方法。

在该代码中,使用promisify()函数将fs.readFile()方法转换为一个返回Promise对象的函数readFile()。接着,使用await关键字调用readFile()方法读取指定路径下的package.json文件,并将读取到的数据转换为一个JavaScript对象。最后,将该对象的name属性输出到控制台。

这种方式可以使代码更加简洁和易读,避免了回调函数嵌套的问题,提高了代码的可维护性和可读性。

总结

使用 promisify 包装后,可以通过 Promise 的链式调用或者使用await获取回调的内容,使你的代码更加简洁优雅。结尾再复习一下node模块es6 调用的方式,如下:

import util from 'node:util'

这里使用了Node.js中的特殊语法node:util,表示从Node.js的内置模块util中导入模块。这种语法只能在Node.js环境中使用,在浏览器环境中无法使用。

在导入util模块后,可以使用其中的方法和类来简化代码,例如使用util.promisify()方法将一个回调函数转换为一个返回Promise对象的函数,或者使用util.inherits()方法实现继承等。