本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
学习目标
1. 学习ES6的Reflect
2. promisify的实现原理
源码地址
使用案例
// 获取node版本
import { promisify } from 'node:util'
import { execFile } from 'node:child_process'
const execFilePromise = promisify(execFile)
const { stdout } = await execFilePromise('node', ['--version'])
console.log(stdout)
- node的内置模块可以加
node:前缀 child_process.execFile()与child_process.exec()类似,区别是它默认不生成新的shell- node中的异步编程依托于回调实现,类似于:
import {readFile} from 'fs'
readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行结束!");
源码解读
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
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
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的实现,代码如下:
function promisify(original) {
return function(...args) {
return new Promise((resolve, reject) => {
args.push((err, value) => {
if(err) {
reject()
} else {
resolve(value)
}
})
// 相当于original.apply(this, args)
Reflect.apply(original, this, args)
})
}
}
实现细节
- 将原函数作为参数调用
promisify返回一个包装后函数fn - 调用
fn并传入原函数需要的参数,在原参数后面追加一个回调函数 - 回调函数的第一个参数是错误信息
err,第二个参数是Promise的返回值,根据是否有错误信息改变Promise的状态 - 执行原函数并传入处理后的参数
args
使用的时候可以在原函数的最后一个参数传入callback供异步调用
const loadImg = function(src, callback) {
const img = document.createElement('img')
img.src = src
img.style = 'width: 200px;height: 280px';
img.onload = callback(null, src) // 正常加载时,err传null
img.onerror = callback(new Error('图片加载失败'))
document.body.append(img);
}
const loadImgPromise = promisify(loadImg)
const load = async () => {
try {
const res = await loadImgPromise('https://cdn.huaban.com/home/202201210156-1e92.png')
console.log(res)
} catch(err) {
console.log(err)
}
}
load()
Reflect
Reflect是ES6为了操作对象而提供的新的API。 具体的可以参考 reflect 简单总结一下他的作用:
Reflect对象上部署了语言内部的属性(比如Object.defineProperty),可以完全取代Object的语言内部属性来使用- 合理化某些
Object方法的返回结果(比如defineProperty某个属性时,无法定义时返回false,之前是报错)。 - 让
Object操作都变成函数行为。某些命令式操作,比如name in obj和delete obj[name],可以使用Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)这种函数形式。 Reflect对象的方法与Proxy对象的方法一一对应。
// reflect 使用案例
var myObject = {
foo: 1,
get baz() {
return this.foo; // this默认读取当前对象 当传入receiver时,this绑定receiver
},
set bar(val) {
this.foo = val // this默认读取当前对象 当传入receiver时,this绑定receiver
},
};
var myReceiverObject = {
foo: 4
};
Reflect.get(myObject, 'baz')
console.log( myObject.foo); // 1
// 传入receiver,绑定getter函数的this
Reflect.get(myObject, 'baz', myReceiverObject)
console.log(myReceiverObject.foo); // 4
Reflect.set(myObject, 'bar', 3)
console.log(myObject.foo); // 3
// 传入receiver,绑定setter函数的this
Reflect.set(myObject, 'bar', 5, myReceiverObject)
console.log(myObject.foo); // 3
console.log(myReceiverObject.foo); // 5
Reflect经常配合Proxy使用,Proxy的作用是 拦截,Reflect的作用是 赋值
这里有个注意点:
赋值操作使用Reflect.set,并且传入receiver时,会触发Proxy.defineProperty,不传入receiver则不会触发
const obj = {a: 1, b: 2, c: 3}
const objPro = new Proxy(obj, {
set(target, key, value, receiver) {
console.log('Reflect.set')
// 赋值操作使用Reflect.set,并且传入receiver时,会触发Proxy.defineProperty
Reflect.set(target, key, value, receiver)
},
defineProperty(target, key, desc) {
console.log('defineProperty')
}
})
objPro.a = 2
// Reflect.set
// defineProperty
总结
promisify和Reflect都是经常会用到的,要学以致用。