【若川视野 x 源码共读】第14期 | 将callback进行promisify化处理

107 阅读2分钟

前言

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

源码

今天在阅读Node util源码的时候,了解到一个函数promisify,是将callback转为promise处理,避免了callback的回调地域,觉得很好玩,一起来看下源码吧。

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
      // 向args里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
      // 类似于original.apply(this, args)
      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

简单案例

假使有个用JS加载图片的需求。推荐一个生成图片的接口,很好用!

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);
}
​
loadImage(imageSrc, function(err, content){
  if(err){
    console.log(err);
    return;
  }
  console.log(content);
});

很容易发现,我们是用callback来处理异步函数,我们尝试通过promise来优化地狱回调的问题。

初步优化

我们将Promiseresolvereject通过callback来处理,这个想法非常棒,将内部声明的变量通过callback交给外部去处理,给予开发人员更便捷的控制方案。

const loadImagePromise = function(src){
  return new Promise(function(resolve, reject){
    loadImage(src, function (err, image) {
      if(err){
        reject(err);
        return;
      }
      resolve(image);
    });
  });
};
​
loadImagePromise(imageSrc).then(res => {
    console.log(res);
})
.catch(err => {
    console.log(err);
});

你看,我们还是比较容易去实现的,但是这个有个弊端,不能通用,需要在使用的地方重复的声明,这明显不符合我们的编程思想,接下来我们一起来优化下。

通用型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 loadImagePromise = promisify(loadImage);
async function load(){
  try{
    const res = await loadImagePromise(imageSrc);
    console.log(res);
  }
  catch(err){
    console.log(err);
  }
}
load();

知识拓展

Reflect

Reflect是ES6提供的新对象

  1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty,Proxy对象上所有新增函等),放到Reflect对象上。
  2. 让Object操作都变成函数行为,将对象的操作符操作封装为函数以让用户明显感知操作的成败。修改某些Object方法的返回结果,让其变得更合理。

充电:Reflect常用的静态方法