一、简介
在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格式的函数
(完)
网友评论