美文网首页饥人谷技术博客
Node.js如何实现 callback 和 promise 的

Node.js如何实现 callback 和 promise 的

作者: _茂 | 来源:发表于2019-03-28 17:43 被阅读10次

一、简介

在Node.js中,提供了callback和promise互相转换的API.

输入:异步函数,可以是async关键字,或者Promise等等
输出:错误优先的回调函数

输入:错误优先的回调函数
输出:Promise函数

注:这里的‘错误优先的回调函数’指的是像这样:
(err, value) => { // TODO } 的函数,这种函数总是把错误作为第一个参数,数据作为第二个参数,这样便于管理整个错误链,在回调时可以适时地处理error

二、源码

1.callbackify

这是额外添加了注释的util.callbackify(original)源码

// 错误时的callback
function callbackifyOnRejected(reason, cb) {
  if (!reason) {
    const newReason = new ERR_FALSY_VALUE_REJECTION();
    newReason.reason = reason;
    reason = newReason;
    Error.captureStackTrace(reason, callbackifyOnRejected);
  }
  return cb(reason);
}
// 主函数
function callbackify(original) {
  if (typeof original !== 'function') {
    throw new ERR_INVALID_ARG_TYPE('original', 'Function', original);
  }

  // 这里官方指出,不返回promise的原因是,不希望造成一种假象:
  // (假象) promise与callback的执行关联,抛出的callback会reject这个promise
  function callbackified(...args) {
    // 取最后一个参数,应该就是callback
    const maybeCb = args.pop();
    // 如果callback不是函数,报错
    if (typeof maybeCb !== 'function') {
      throw new ERR_INVALID_ARG_TYPE('last argument', 'Function', maybeCb);
    }
    // 利用[Reflect.apply](不知道的可以看 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply ,
    // 类似 Function.prototype.apply )
    // 执行函数
    const cb = (...args) => { Reflect.apply(maybeCb, this, args); };
    // 1.执行原函数
    // 2.利用nextTick,在执行原函数后,分别把成功和失败的callback放到next tick的队列中
    //(这个队列中的函数会在这次 Node的‘事件循环’ 结束后执行)
    // 这里用到Promise.prototype.then(onFulfilled, onRejected)方法,
    // 处理成功和失败的callback
    Reflect.apply(original, this, args)
      .then((ret) => process.nextTick(cb, null, ret),
            (rej) => process.nextTick(callbackifyOnRejected, rej, cb));
  }
  // 把新生成的函数callbackified的原型指向原函数的原型
  Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original));
  // 把原函数的其他属性定义,复制一份,应用到新函数callbackified上
  Object.defineProperties(callbackified,
                          Object.getOwnPropertyDescriptors(original));
  return callbackified;
}
2.promisify

这是额外添加了注释的util.promisify(original)源码

// 此属性在promisify后,会挂在新函数下,用来存储被promisify化后的新函数;
// 也在自定义promisify函数时使用(文档中指明,自定义时,覆盖[util.promisify.custom]属性即可)
const kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
// 此属性用来存储callback的参数名数组
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');

function promisify(original) {
  // 如果传入的不是函数,则Throw Error
  if (typeof original !== 'function')
    throw new ERR_INVALID_ARG_TYPE('original', 'Function', original);
  // 如果原函数拥有属性[kCustomPromisifiedSymbol](说明此函数已经被promisify化过,或者有自定义的promisify函数)
  if (original[kCustomPromisifiedSymbol]) {
    const fn = original[kCustomPromisifiedSymbol];
    // 如果传入的不是函数,则Throw Error
    if (typeof fn !== 'function') {
      throw new ERR_INVALID_ARG_TYPE('util.promisify.custom', 'Function', fn);
    }
    // 已经promisify过的函数 或 有自定义promisify函数的,不需要转化,直接设置一下        
    // [kCustomPromisifiedSymbol]属性即可
    Object.defineProperty(fn, kCustomPromisifiedSymbol, {
      value: fn, enumerable: false, writable: false, configurable: true
    });
    // 改写后,即可返回
    return fn;
  }

  // 创建一个用来存储传入参数的对象,以便接收callback的多个参数名,不是参数值(!!不是很理解[kCustomPromisifyArgsSymbol]属性的由来,可能要等继续深入,串起来看!!)
  // 比如child_process.exec(command[, options][, callback])这个API的callback: function(error, stdout, stderr){ // TODO },
  // 那么这个属性就会存储 ['stdout', 'stderr']
  const argumentNames = original[kCustomPromisifyArgsSymbol];

  function fn(...args) {
    // 创建Promise对象
    return new Promise((resolve, reject) => {
      // 调用原函数original
      original.call(this, ...args, (err, ...values) => {
        if (err) {
          return reject(err);
        }
        // 如果有多个callback参数,把参数名 和 参数值,一一对应,存储到obj上,再通过resolve传输
        if (argumentNames !== undefined && values.length > 1) {
          const obj = {};
          for (var i = 0; i < argumentNames.length; i++)
            obj[argumentNames[i]] = values[i];
          resolve(obj);
        } else {
          // 如果values只有一个参数,直接通过resolve传输
          resolve(values[0]);
        }
      });
    });
  }
  // 把新生成的函数fn的原型指向原函数的原型
  Object.setPrototypeOf(fn, Object.getPrototypeOf(original));
  // 把新生成的函数fn的[kCustomPromisifiedSymbol]属性改为fn
  Object.defineProperty(fn, kCustomPromisifiedSymbol, {
    value: fn, enumerable: false, writable: false, configurable: true
  });
  // 把原函数的其他属性定义,复制一份,应用到新函数上
  return Object.defineProperties(
    fn,
    Object.getOwnPropertyDescriptors(original)
  );
}

三、总结

不管是 util.callbackify(original) 还是 util.promisify(original) ,都是创建并返回一个新生成的函数,这个函数会利用闭包,执行原有的函数逻辑,并包装整理,格式化成对应的callback或promise格式的函数

(完)

相关文章

网友评论

    本文标题:Node.js如何实现 callback 和 promise 的

    本文链接:https://www.haomeiwen.com/subject/yrnzvqtx.html