支持链式操作
我们平时写promise一般都是对应的一组流程化的操作,如这样: promise.then(f1).then(f2).then(f3) 但是我们之前的版本最多只能注册一个回调,这一节我们就来实现链式操作。
目标
使promise支持链式操作
实现
想支持链式操作,其实很简单,首先存储回调时要改为使用数组
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
当然执行回调时,也要改成遍历回调数组执行回调函数
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
最后,then方法也要改一下,只需要在最后一行加一个return this即可,这其实和jQuery链式操作的原理一致,每次调用完方法都返回自身实例,后面的方法也是实例的方法,所以可以继续执行。
Promise.prototype.then = function(onFulfilled, onRejected){
let self = this;
if(self.state === 'resolved'){
onFulfilled(self.value);
}
if(self.state === 'rejected'){
onRejected(self.reson);
}
if(self.state === 'padding'){
self.onResolvedCallbacks.push(function(){
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(function(){
onRejected(self.reson);
});
}
return this;
}
这种简单返回this会有一个问题,这里为方便讲解我们引入一个常见场景:用promise顺序读取文件内容,场景代码如下:
let p = new Promise((resolve, reject) => {
fs.readFile('../file/1.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
let f1 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/2.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f2 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/3.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f3 = function(data) {
console.log(data);
}
let errorLog = function(error) {
console.log(error)
}
p.then(f1).then(f2).then(f3).catch(errorLog)
上面场景,我们读取完1.txt后并打印1.txt内容,再去读取2.txt并打印2.txt内容,再去读取3.txt并打印3.txt内容,而读取文件都是异步操作,所以都是返回一个promise,我们上一节实现的promise可以实现执行完异步操作后执行后续回调,但是本节的回调读取文件内容操作并不是同步的,而是异步的,所以当读取完1.txt后,执行它回调onFulfilledCallbacks里面的f1,f2,f3时,异步操作还没有完成,所以我们本想得到这样的输出:
this is 1.txt
this is 2.txt
this is 3.txt
但是实际上却会输出
this is 1.txt
this is 1.txt
this is 1.txt
所以要想实现异步操作串行,我们不能将回调函数都注册在初始promise的onFulfilledCallbacks里面,而要将每个回调函数注册在对应的异步操作promise的onFulfilledCallbacks里面,用读取文件的场景来举例,f1要在p的onFulfilledCallbacks里面,而f2应该在f1里面return的那个Promise的onFulfilledCallbacks里面,因为只有这样才能实现读取完2.txt后才去打印2.txt的结果。
但是,我们平常写promise一般都是这样写的: promise.then(f1).then(f2).then(f3),一开始所有流程我们就指定好了,而不是在f1里面才去注册f1的回调,f2里面才去注册f2的回调。
如何既能保持这种链式写法的同时又能使异步操作衔接执行呢?我们其实让then方法最后不再返回自身实例,而是返回一个新的promise即可,我们可以叫它bridgePromise,它最大的作用就是衔接后续操作,我们看下具体实现代码:
function Promise(executor){
let self = this;
self.state = 'padding';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.state == 'padding'){
self.state = 'resolved'
self.value = value
self.onResolvedCallbacks.forEach(fn=>fn());
}
}
function reject(reason){
if(self.state == 'padding'){
self.state = 'rejected'
self.reason = reason
self.onRejectedCallbacks.forEach(fn=>fn());
}
}
try{
executor(resolve, reject);
}catch(e){
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
let self = this;
//不能写成:let promise2 = new Promise(function(resolve, reject){}}
let promise2;
promise2 = new Promise(function(resolve, reject){
if(self.state === 'resolved'){
try{
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}
if(self.state === 'rejected'){
try{
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}
if(self.state === 'padding'){
self.onResolvedCallbacks.push(function(){
try{
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
});
}
});
return promise2;
}
/**
* [resolvePromise description]
* @param {[type]} promise2 [then的返回值(新的promise)]
* @param {[type]} x [then种成功或失败的返回值]
* @param {[type]} resolve [promise2中的resolve]
* @param {[type]} reject [promise2中的reject]
* @return {[type]} [description]
*/
function resolvePromise(promise2, x, resolve, reject){
//console.log('resolvePromise');
if(promise2 === x){
return reject(new TypeError('循环引用promise'));
}
if(x!==null && (typeof x === 'object' || typeof x === 'function')){
let then = x.then;
if(typeof then === 'function'){//说明返回的是promise,只有promise上才有then
console.log('function');
then.call(
x,
y=>resolve(y),
err=>reject(err)
);
}else{//说明返回的是 普通对象
console.log('object');
resolve(x);
}
}else{//说明返回的是 普通值
resolve(x);
}
}
这里很抽象,我们还是以文件顺序读取的场景画一张图解释一下流程:
当执行p.then(f1).then(f2).then(f3)时:
- 先执行p.then(f1)返回了一个bridgePromise(p2),并在p的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f1并且更新p2的状态.
- 然后.then(f2)时返回了一个bridgePromise(p3),这里注意其实是p2.then(f2),因为p.then(f1)时返回了p2。此时在p2的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f2并且更新p3的状态.
- 然后.then(f3)时返回了一个bridgePromise(p4),并在p3的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f3并且更新p4的状态. 到此,回调关系注册完了,如图所示:
-
然后过了一段时间,p里面的异步操作执行完了,读取到了1.txt的内容,开始执行p的回调函数,回调函数执行f1,打印出1.txt的内容“this is 1.txt”,并将f1的返回值放到resolvePromise中开始解析。resolvePromise一看传入了一个promise对象,promise是异步的啊,得等着呢,于是就在这个promise对象的then方法中继续resolvePromise这个promise对象resolve的结果,一看不是promise对象了,而是一个具体值“this is 2.txt”,于是调用bridgePromise(p2)的reslove方法将bridgePromise(p2)的状态更新为fulfilled,并将“this is 2.txt”传入p2的回调函数中去执行。
-
p2的回调开始执行,f2拿到传过来的“this is 2.txt”参数开始执行,打印出2.txt的内容,并将f2的返回值放到resolvePromise中开始解析,resolvePromise一看传入了一个promise对象,promise是异步的啊,又得等着呢........后续操作就是不断的重复4,5步直到结束。