从22行有趣的源码库中,我学到了 callback promisify 化的 Node.js 源码实现

124 阅读2分钟

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

【若川视野 x 源码共读】第14期 | promisify 【若川视野 x 源码共读】第14期 | promisify - 掘金 (juejin.cn)

2.callback callback callback 一定要记得是callback

2.1简化版

const imageSrc = 'https://www.themealdb.com/images/ingredients/Lime.png';

function loadImage(src, callback) {
    const image = document.createElement('img');
    image.src = src;
    image.alt = '公众号若川视野专用图?';
    image.style = 'width: 200px;height: 200px';
    image.onload = () => callback(null, image);
    image.onerror = () => callback(new Error('加载失败'));
    document.body.append(image);
}
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 loadImagePromise = promisify(loadImage);
        async function load(){
            try{
                const res = await loadImagePromise(imageSrc);
                console.log(res);
            }
            catch(err){
                console.log(err);
            }
        }
        load();

image.png loadImage.js文件创建一张图片并进行挂载到页面上,需要注意的是onload和onerror其箭头函数返回的是cb回调i函数; promisify.js文件中Reflect.apply(original, this, args);Reflect其实和Proxy可以对对象的属性进行拦截,注意的是第三个参数传入一个箭头函数,该箭头函数会在图片请求成功进行执行onload的内callback,而这个时候就会再次执行传入前的箭头函数,如果错误就进行调用err. (callback 在开发中也经常碰到类似场景,比如在一个特殊弹窗组件A封装到一个B组件中,正常情况下是在组件A进行打开弹窗,但是现在突然也需要这样一个弹窗组件A,通过是否确定执行下一步,而我正常操作是在打开组件A传入箭头函数并保存,在弹窗A点击确定的时候通过判断是否有cb)

整体代码

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, {
      value: fn, enumerable: false, writable: false, configurable: true
    });
  }

  // Names to create an object from in case the callback receives multiple
  // arguments, e.g. ['bytesRead', 'buffer'] for fs.read.
  const argumentNames = original[kCustomPromisifyArgsSymbol];

  function fn(...args) {
    return new Promise((resolve, reject) => {
      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(original, this, args);
    });
  }

  ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));

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

promisify.custom = kCustomPromisifiedSymbol;