美文网首页饥人谷技术博客
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