写在前面
铺天盖地的Promise文章还没完全消散,Promise本身就被ES7劝退了
关于Promise的具体语法及应用场景,可以查看:
至于另一篇(动手实现promise),不建议看,这个实现存在的问题很多(性能、未知bug等等),也不打算继续维护,千万不要用
Promise作为一种异步流程控制方法,本篇介绍其实现机制,并模拟实现基本功能
一.基础部分
Promise内部分为Promise对象与Deferred对象,前者提供接口接收handler,后者记录状态,维持异步逻辑正确执行(男主外女主内的感觉,promise.then()
接收handler,内部的deferred
负责维护状态)
Promise
Promise主外,接收handler并注册给事件机制
简单情况下,Promise可以直接套用EventEmitter,如下:
var EventEmitter = require('events');
var util = require('util');
//--- 自定义promise
var MyPromise = function() {
EventEmitter.call(this);
};
util.inherits(MyPromise, EventEmitter);
然后公开then()
,负责接收handler:
MyPromise.prototype.then = function(onFulfilled, onRejected, onProgress) {
if (typeof onFulfilled === 'function') {
this.once('resolve', onFulfilled);
}
if (typeof onRejected === 'function') {
this.once('reject', onRejected);
}
if (typeof onProgress === 'function') {
this.on('progress', onProgress);
}
};
主外的就结束了,完全看不到将要成为Promise的端倪(现在确实不像,因为这个Promise不是最终暴露出去的Promise)
Deferred
Deferred要维护状态,稍复杂一些
初始状态,如下:
var Deferred = function() {
this.state = 'pending';
this.promise = new MyPromise();
};
Deferred实例持有promise
引用以便触发事件执行handler(主内的管着主外的)
然后把事件机制与状态联系起来:
Deferred.prototype.resolve = function(res) {
this.promise.emit('resolve', res);
this.state = 'resolved';
};
Deferred.prototype.reject = function(err) {
this.promise.emit('reject', err);
this.state = 'rejected';
};
Deferred.prototype.progress = function(chunk) {
this.promise.emit('progress', chunk);
};
最基础的部分完成了,最后提供一个Promise并暴露出去:
module.exports = {
Promise: MyPromise,
Deferred: Deferred,
MyPromise: function(fn) {
// 向fn注入reslove和reject
var deferred = new Deferred();
fn.call(deferred, (res) => {
process.nextTick(() => {deferred.resolve(res);});
}, (err) => {
process.nextTick(() => {deferred.reject(err);});
});
return deferred.promise;
}
};
module.exports.MyPromise
就是最终产物,延迟执行reslove/reject
是为了等待外部的promise.then()
,也就是先让handler同步注册,然后再触发事件,这也是Promise不用预先指定分支及延迟逻辑处理的秘密所在
示例如下:
var P = require('./p.js');
var p1 = new P.MyPromise(function(resolve, reject) {
console.log('#1');
resolve(1);
console.log('#2');
});
p1.then((res) => {console.log(res);});
// log print:
// #1
// #2
// 1
var p2 = new P.MyPromise(function(resolve, reject) {
console.log('#1');
reject(new Error('reject'));
console.log('#2');
});
p2.then(null, (err) => {console.log(err.toString());});
// log print:
// #1
// #2
// Error: reject
二.扩展功能
1.回调函数生成
Node回调函数大都遵循callback(err, res1, res...)
的规则,可以据此生成回调函数:
// node callback
Deferred.prototype.callback = function() {
return (err, value) => {
if (err) {
this.reject(err);
}
else if (arguments.length > 2) {
this.resolve(Array.prototype.slice.call(arguments, 1));
}
else {
this.resolve(value);
}
};
};
可以简化promisify过程,让Promise更好用一些
2.多依赖异步控制
各个异步库都提供了依赖控制方法,比如大而全的async
模块提供的series()
, parallel()
, waterfall()
等等
Promise提供的依赖控制不够多,只支持all()
, race()
,这里模拟实现all()
,如下:
// 多依赖异步控制
Deferred.prototype.all = function(promises) {
var results = [];
var count = promises.length;
promises.forEach((promise, i) => {
promise.then((data) => {
results[i] = data;
count--;
if (count === 0) {
this.resolve(results);
}
}, (err) => {
this.reject(err);
});
});
return this.promise;
};
用法示例:
// 多依赖异步控制
var fs = require('fs');
// promisify
var readFile = function(file, encoding) {
encoding = encoding || 'utf-8';
var deferred = new P.Deferred();
fs.readFile(file, encoding, deferred.callback());
return deferred.promise;
};
// test
var p1 = readFile('./p.js');
var p2 = readFile('./index.js');
var d1 = new P.Deferred();
d1.all([p1, p2]).then((arrRes) => {
var aRes = arrRes.map((res) => {
return res.toString().slice(0, 20);
});
console.log(aRes);
}, (err) => {
console.log(err);
});
// log print:
// [ 'var EventEmitter = r', 'var P = require(\'./p' ]
这里没有把Deferred包装起来,更清晰一些(其实是懒得包装了- -)
3.Promise链
支持Promise链需要维护一个队列,并根据队列中每个promise
的状态手动控制,偷懒的事件机制不再适用了(嗯,需要大改)
Promise
剔除事件机制,采用任务队列
//--- 自定义promise
var MyPromise = function() {
// 用于支持promise链
this.queue = [];
this.isPromise = true;
};
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 采用任务队列手动控制,不利用事件机制触发执行了
var handler = {};
if (typeof onFulfilled === 'function') {
handler.onFulfilled = onFulfilled;
}
if (typeof onRejected === 'function') {
handler.onRejected = onRejected;
}
this.queue.push(handler);
return this;
};
Deferred
手动执行回调,并传递结果
var Deferred = function() {
this.state = 'pending';
this.promise = new MyPromise();
};
Deferred.prototype.resolve = function(res) {
var handler;
while (handler = this.promise.queue.shift()) {
if (handler && handler.onFulfilled) {
// 执行肯定回调
var ret = handler.onFulfilled(res);
// 如果肯定回调的返回值为promise,更新this.promise
if (ret && ret.isPromise) {
ret.queue = this.promise.queue;
this.promise = ret;
return;
}
}
}
};
Deferred.prototype.reject = function(err) {
var handler;
while (handler = this.promise.queue.shift()) {
if (handler && handler.onRejected) {
var ret = handler.onRejected(err);
if (ret && ret.isPromise) {
ret.queue = this.promise.queue;
this.promise = ret;
return;
}
else {
// 把ret传入下一个handler的onFulfilled
var nextHandler = this.promise.queue.shift();
if (nextHandler && nextHandler.onFulfilled) {
nextHandler.onFulfilled(ret);
}
return;
}
}
}
};
用法示例如下:
// promise链
var NewP = require('./newp.js');
// promisify
var readFile = function(file, encoding) {
encoding = encoding || 'utf-8';
var deferred = new NewP.Deferred();
fs.readFile(file, encoding, deferred.callback());
return deferred.promise;
};
// test resolve
readFile('./p.js').then((data) => {
return readFile('./index.js');
}).then((data) => {
console.log('index: ' + data.slice(0, 20));
});
// log print:
// index: var P = require('./p
// test reject
readFile('./a.bcd').then((data) => {
return readFile('./p.js');
}, (err) => {
console.log('!!!error occurs: ' + err);
return 'something for next onFulfilled';
}).then((data) => {
console.log(data);
});
// log print:
// !!!error occurs: Error: ENOENT: no such file or directory, open...
// something for next onFulfilled
4.smooth
smooth表示promisify现有API,技巧是篡改arguments
,如下:
var smooth = (method) => {
return function() {
var deferred = new P.Deferred();
var args = Array.prototype.slice.call(arguments, 0);
args.push(deferred.callback());
method.apply(null, args);
return deferred.promise;
};
};
由smooth内部偷偷提供callback
,用法很简洁:
var readFile = smooth(fs.readFile);
readFile('./index.js', 'utf-8').then((res) => {
console.log(res.slice(0, 20));
});
异步方法的回调函数从代码中消失了。这个技巧对于“正版Promise”同样适用,需要用Promise包装大量API时可以考虑
三.总结
Promise在舞台上转了一圈,然后没能登上领奖台
作为异步流程控制方案,Promise最大的问题就是存在封装(promisify)成本,而且提供的多依赖控制方法不够用,需要自行扩展,所以,不好用,不好用就不会长久
过时了就没什么好说的,还记得当时铺天盖地的《关于Promise你不知道的…》、《你真的会用Promise吗》
参考资料
- 《深入浅出NodeJS》