Node.js的promisify源码解析

1,115 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

学习目标

1. 学习ES6的Reflect
2. promisify的实现原理

源码地址

node util源码

使用案例

// 获取node版本
import { promisify } from 'node:util'
import { execFile } from 'node:child_process'
const execFilePromise = promisify(execFile)
const { stdout } = await execFilePromise('node', ['--version'])
console.log(stdout)
  • node的内置模块可以加node:前缀
  • child_process.execFile()child_process.exec()类似,区别是它默认不生成新的shell
  • node中的异步编程依托于回调实现,类似于:
import {readFile} from 'fs'
readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("程序执行结束!");

源码解读

const kCustomPromisifiedSymbol = SymbolFor('nodejs.util.promisify.custom');
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');

let validateFunction;

function promisify(original) {
  // Lazy-load to avoid a circular dependency.
  if (validateFunction === undefined)
    ({ validateFunction } = require('internal/validators'));

  validateFunction(original, 'original');

  if (original[kCustomPromisifiedSymbol]) {
    const fn = original[kCustomPromisifiedSymbol];

    validateFunction(fn, 'util.promisify.custom');

    return ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
      __proto__: null,
      value: fn, enumerable: false, writable: false, configurable: true
    });
  }

  const argumentNames = original[kCustomPromisifyArgsSymbol];
  // 核心实现 start
  function fn(...args) {
    return new Promise((resolve, reject) => {
      // ArrayPrototypePush = Array.prototype.push
      ArrayPrototypePush(args, (err, ...values) => {
        if (err) {
          return reject(err);
        }
        if (argumentNames !== undefined && values.length > 1) {
          const obj = {};
          for (let i = 0; i < argumentNames.length; i++)
            obj[argumentNames[i]] = values[i];
          resolve(obj);
        } else {
          resolve(values[0]);
        }
      });
      // ReflectApply = Reflect.apply
      ReflectApply(original, this, args);
    });
  }
  // 核心实现 end
  ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));

  ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
    __proto__: null,
    value: fn, enumerable: false, writable: false, configurable: true
  });
  return ObjectDefineProperties(
    fn,
    ObjectGetOwnPropertyDescriptors(original)
  );
}

promisify简化实现

我们可以简化一下promisify的实现,代码如下:

function promisify(original) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            args.push((err, value) => {
                if(err) {
                    reject()
                } else {
                    resolve(value)
                }
            })
            // 相当于original.apply(this, args)
            Reflect.apply(original, this, args)
        })
    }
}

实现细节

  • 将原函数作为参数调用promisify返回一个包装后函数fn
  • 调用fn并传入原函数需要的参数,在原参数后面追加一个回调函数
  • 回调函数的第一个参数是错误信息err,第二个参数是Promise的返回值,根据是否有错误信息改变Promise的状态
  • 执行原函数并传入处理后的参数args

使用的时候可以在原函数的最后一个参数传入callback供异步调用

const loadImg = function(src, callback) {
        const img = document.createElement('img')
        img.src = src
        img.style = 'width: 200px;height: 280px';
        img.onload = callback(null, src) // 正常加载时,err传null
        img.onerror = callback(new Error('图片加载失败'))
        document.body.append(img);
    }
const loadImgPromise = promisify(loadImg)
const load = async () => {
    try {
        const res = await loadImgPromise('https://cdn.huaban.com/home/202201210156-1e92.png')
        console.log(res)
    } catch(err) {
        console.log(err)
    }
}
load()

Reflect

Reflect是ES6为了操作对象而提供的新的API。 具体的可以参考 reflect 简单总结一下他的作用:

  1. Reflect对象上部署了语言内部的属性(比如Object.defineProperty),可以完全取代Object的语言内部属性来使用
  2. 合理化某些Object方法的返回结果(比如defineProperty某个属性时,无法定义时返回false,之前是报错)。
  3. Object操作都变成函数行为。某些命令式操作,比如name in objdelete obj[name],可以使用Reflect.has(obj, name)Reflect.deleteProperty(obj, name)这种函数形式。
  4. Reflect对象的方法与Proxy对象的方法一一对应。
// reflect 使用案例

var myObject = {
    foo: 1,
    
    get baz() {
      return this.foo; // this默认读取当前对象 当传入receiver时,this绑定receiver
    },
    set bar(val) {
        this.foo = val // this默认读取当前对象 当传入receiver时,this绑定receiver
    },
  };
  
var myReceiverObject = {
foo: 4
};
Reflect.get(myObject, 'baz')
console.log( myObject.foo); // 1
// 传入receiver,绑定getter函数的this
Reflect.get(myObject, 'baz', myReceiverObject)
console.log(myReceiverObject.foo); // 4

Reflect.set(myObject, 'bar', 3)
console.log(myObject.foo); // 3
// 传入receiver,绑定setter函数的this
Reflect.set(myObject, 'bar', 5, myReceiverObject) 
console.log(myObject.foo); // 3
console.log(myReceiverObject.foo); // 5

Reflect经常配合Proxy使用,Proxy的作用是 拦截Reflect的作用是 赋值

这里有个注意点:

赋值操作使用Reflect.set,并且传入receiver时,会触发Proxy.defineProperty,不传入receiver则不会触发

const obj = {a: 1, b: 2, c: 3}
const objPro = new Proxy(obj, {
    set(target, key, value, receiver) { 
        console.log('Reflect.set')
        // 赋值操作使用Reflect.set,并且传入receiver时,会触发Proxy.defineProperty
        Reflect.set(target, key, value, receiver)
    },
    defineProperty(target, key, desc) { 
        console.log('defineProperty')
    }
})
objPro.a = 2
// Reflect.set
// defineProperty

总结

promisifyReflect都是经常会用到的,要学以致用。