前言
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
源码
今天在阅读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来优化地狱回调的问题。
初步优化
我们将Promise的resolve和reject通过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提供的新对象
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty,Proxy对象上所有新增函等),放到Reflect对象上。
- 让Object操作都变成函数行为,将对象的操作符操作封装为函数以让用户明显感知操作的成败。修改某些Object方法的返回结果,让其变得更合理。