我把then方法的执行作成同步的了,是不符合规范的。javascript
[《Promises/A+规范》][6]中,【Then 方法】小节【调用时机】部分写道:“onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用”,这里特别要看一下注释。html
所以我要把onFulfilled
和 onRejected
的代码放在“ then
方法被调用的那一轮事件循环以后的新执行栈中执行”,经过setTimeout
方法将任务放到本轮任务队列的末尾。代码已添加到最后一部分-第九步。java
关于任务队列的运行机制,感兴趣可看一下阮一峰老师的《JavaScript 运行机制详解:再谈Event Loop》git
Promise
基本功能,与原生同样,异步、同步操做均ok,具体包括:
MyPromise.prototype.then()
MyPromise.prototype.catch()
与原生 Promise
略有出入MyPromise.prototype.finally()
MyPromise.all()
MyPromise.race()
MyPromise.resolve()
MyPromise.reject()
rejected
状态的冒泡处理也已解决,当前Promise的reject若是没有捕获,会一直冒泡到最后,直到catchMyPromise
状态一旦改变,将不能再改变它的状态index.html
index.js
边看代码边玩;MyPromise
的运行结果,下面是原生 Promise
运行的结果;Promise
先是弄懂他,再去思考他,最后一步步把功能实现出来,怼他的理解不断加深,愈来愈透彻;then/catch
方法是最难的,要不停地修修补补;reject
状态的冒泡是个难题,但在下面的代码中我没有专门说起,我也没有办法具体说清楚他,我是在整个过程当中不停地调才最终调出来正确的冒泡结果。下面贴代码,包括整个思考过程,会有点长
为了说明书写的逻辑,我使用如下几个注释标识,整坨变更的代码只标识这一坨的开头处。
//++
——添加的代码
//-+
——修改的代码es6
名字随便取,个人叫MyPromise,没有取代原生的Promise。github
callback
。当新建 MyPromise
对象时,咱们须要运行此回调,而且 callback
自身也有两个参数,分别是 resolve
和 reject
,他们也是回调函数的形式;callback
时,若是是 resolve
状态,将结果保存在 this.__succ_res
中,状态标记为成功;若是是 reject
状态,操做相似;then
方法,是一个原型方法;then
方法时,判断对象的状态是成功仍是失败,分别执行对应的回调,把结果传入回调处理;...arg
和传入参数 ...this.__succ_res
都使用了扩展运算符,为了应对多个参数的状况,原封不动地传给 then
方法回调。
callback
回调这里使用箭头函数,this
的指向就是本当前MyPromise
对象,因此无需处理this
问题。json
class MyPromise {
constructor(callback) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
//箭头函数绑定了this,若是使用es5写法,须要定义一个替代的this
callback((...arg) => {
this.__succ_res = arg;
this.status = 'success';
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
});
}
then(onFulfilled, onRejected) {
if (this.status === 'success') {
onFulfilled(...this.__succ_res);
} else if (this.status === 'error') {
onRejected(...this.__err_res);
};
}
};
复制代码
到这里,MyPromise
能够简单实现一些同步代码,好比:segmentfault
new MyPromise((resolve, reject) => {
resolve(1);
}).then(res => {
console.log(res);
});
//结果 1
复制代码
执行异步代码时,then
方法会先于异步结果执行,上面的处理还没法获取到结果。数组
then
方法在 pending
状态时就执行了,因此添加一个 else
;else
时,咱们尚未结果,只能把须要执行的回调,放到一个队列里,等须要时执行它,因此定义了一个新变量 this.__queue
保存事件队列;this.__queue
队列里的回调通通执行一遍,若是是 resolve
状态,则执行对应的 resolve
代码。class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列 //++
//箭头函数绑定了this,若是使用es5写法,须要定义一个替代的this
fn((...arg) => {
this.__succ_res = arg;
this.status = 'success';
this.__queue.forEach(json => { //++
json.resolve(...arg);
});
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
this.__queue.forEach(json => { //++
json.reject(...arg);
});
});
}
then(onFulfilled, onRejected) {
if (this.status === 'success') {
onFulfilled(...this.__succ_res);
} else if (this.status === 'error') {
onRejected(...this.__err_res);
} else { //++
this.__queue.push({resolve: onFulfilled, reject: onRejected});
};
}
};
复制代码
到这一步,MyPromise
已经能够实现一些简单的异步代码了。测试用例 index.html
中,这两个例子已经能够实现了。promise
1 异步测试--resolve
2 异步测试--reject
实际上,原生的 Promise
对象的then方法,返回的也是一个 Promise
对象,一个新的 Promise
对象,这样才能够支持链式调用,一直then
下去。。。 并且,then
方法能够接收到上一个then
方法处理return的结果。根据Promise
的特性分析,这个返回结果有3种可能:
MyPromise
对象;then
方法的对象;then
方法返回一个MyPromise
对象,它的回调函数接收resFn
和 rejFn
两个回调函数;handle
函数,接受成功的结果做为参数;handle
函数中,根据onFulfilled
返回值的不一样,作不一样的处理:
onFulfilled
的返回值(若是有),保存为returnVal
;returnVal
是否有then方法,即包括上面讨论的一、2中状况(它是MyPromise
对象,或者具备then
方法的其余对象),对咱们来讲都是同样的;then
方法,立刻调用其then
方法,分别把成功、失败的结果丢给新MyPromise
对象的回调函数;没有则结果传给resFn
回调函数。class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//箭头函数绑定了this,若是使用es5写法,须要定义一个替代的this
fn((...arg) => {
this.__succ_res = arg;
this.status = 'success';
this.__queue.forEach(json => {
json.resolve(...arg);
});
}, (...arg) => {
this.__err_res = arg;
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...arg);
});
});
}
then(onFulfilled, onRejected) {
return new MyPromise((resFn, rejFn) => { //++
if (this.status === 'success') {
handle(...this.__succ_res); //-+
} else if (this.status === 'error') {
onRejected(...this.__err_res);
} else {
this.__queue.push({resolve: handle, reject: onRejected}); //-+
};
function handle(value) { //++
//then方法的onFulfilled有return时,使用return的值,没有则使用保存的值
let returnVal = onFulfilled instanceof Function && onFulfilled(value) || value;
//若是onFulfilled返回的是新MyPromise对象或具备then方法对象,则调用它的then方法
if (returnVal && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {//其余值
resFn(returnVal);
};
};
})
}
};
复制代码
到这里,MyPromise
对象已经支持链式调用了,测试例子: 4 链式调用--resolve
。可是,很明显,咱们还没完成reject
状态的链式调用。
处理的思路是相似的,在定义的errBack
函数中,检查onRejected
返回的结果是否含then
方法,分开处理。值得一提的是,若是返回的是普通值,应该调用的是resFn
,而不是rejFn
,由于这个返回值属于新MyPromise
对象,它的状态不因当前MyPromise
对象的状态而肯定。便是,返回了普通值,未代表reject
状态,咱们默认为resolve
状态。
代码过长,只展现改动部分。
then(onFulfilled, onRejected) {
return new MyPromise((resFn, rejFn) => {
if (this.status === 'success') {
handle(...this.__succ_res);
} else if (this.status === 'error') {
errBack(...this.__err_res); //-+
} else {
this.__queue.push({resolve: handle, reject: errBack}); //-+
};
function handle(value) {
//then方法的onFulfilled有return时,使用return的值,没有则使用保存的值
let returnVal = onFulfilled instanceof Function && onFulfilled(value) || value;
//若是onFulfilled返回的是新MyPromise对象或具备then方法对象,则调用它的then方法
if (returnVal && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {//其余值
resFn(returnVal);
};
};
function errBack(reason) { //++
if (onRejected instanceof Function) {
//若是有onRejected回调,执行一遍
let returnVal = onRejected(reason);
//执行onRejected回调有返回,判断是否thenable对象
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
//无返回或者不是thenable的,直接丢给新对象resFn回调
resFn(returnVal); //resFn,而不是rejFn
};
} else {//传给下一个reject回调
rejFn(reason);
};
};
})
}
复制代码
如今,MyPromise
对象已经很好地支持链式调用了,测试例子:
4 链式调用--resolve
5 链式调用--reject
28 then回调返回Promise对象(reject)
29 then方法reject回调返回Promise对象
由于其它方法对MyPromise.resolve()
方法有依赖,因此先实现这个方法。 先要彻底弄懂MyPromise.resolve()
方法的特性,研究了阮一峰老师的ECMAScript 6 入门对于MyPromise.resolve()
方法的描述部分,得知,这个方法功能很简单,就是把参数转换成一个MyPromise
对象,关键点在于参数的形式,分别有:
MyPromise
实例;thenable
对象;then
方法的对象,或根本就不是对象;处理的思路是:
MyPromise
实例时,无需处理;thenable
对象的话,调用其then
方法,把相应的值传递给新MyPromise
对象的回调;MyPromise.reject()
方法相对简单不少。与MyPromise.resolve()
方法不一样,MyPromise.reject()
方法的参数,会原封不动地做为reject
的理由,变成后续方法的参数。
MyPromise.resolve = (arg) => {
if (typeof arg === 'undefined' || arg == null) {//无参数/null
return new MyPromise((resolve) => {
resolve(arg);
});
} else if (arg instanceof MyPromise) {
return arg;
} else if (arg['then'] instanceof Function) {
return new MyPromise((resolve, reject) => {
arg.then((res) => {
resolve(res);
}, err => {
reject(err);
});
});
} else {
return new MyPromise(resolve => {
resolve(arg);
});
}
};
MyPromise.reject = (arg) => {
return new MyPromise((resolve, reject) => {
reject(arg);
});
};
复制代码
测试用例有8个:18-25
,感兴趣能够玩一下。
MyPromise.all()
方法接收一堆MyPromise
对象,当他们都成功时,才执行回调。依赖MyPromise.resolve()
方法把不是MyPromise
的参数转为MyPromise
对象。
每一个对象执行then
方法,把结果存到一个数组中,当他们都执行完毕后,即i === arr.length
,才调用resolve()
回调,把结果传进去。
MyPromise.race()
方法也相似,区别在于,这里作的是一个done
标识,若是其中之一改变了状态,再也不接受其余改变。
MyPromise.all = (arr) => {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise(function(resolve, reject) {
let i = 0, result = [];
next();
function next() {
//若是不是MyPromise对象,须要转换
MyPromise.resolve(arr[i]).then(res => {
result.push(res);
i++;
if (i === arr.length) {
resolve(result);
} else {
next();
};
}, reject);
};
})
};
MyPromise.race = arr => {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise((resolve, reject) => {
let done = false;
arr.forEach(item => {
//若是不是MyPromise对象,须要转换
MyPromise.resolve(item).then(res => {
if (!done) {
resolve(res);
done = true;
};
}, err => {
if (!done) {
reject(err);
done = true;
};
});
})
})
}
复制代码
测试用例:
6 all方法
26 race方法测试
他们俩本质上是then
方法的一种延伸,特殊状况的处理。
catch代码中注释部分是我原来的解决思路:运行catch时,若是已是错误状态,则直接运行回调;若是是其它状态,则把回调函数推入事件队列,待最后接收到前面reject状态时执行;由于catch直接收reject状态,因此队列中resolve是个空函数,防止报错。
后来看了参考文章3才了解到还有更好的写法,所以替换了。
class MyPromise {
constructor(fn) {
//...略
}
then(onFulfilled, onRejected) {
//...略
}
catch(errHandler) {
// if (this.status === 'error') {
// errHandler(...this.__err_res);
// } else {
// this.__queue.push({resolve: () => {}, reject: errHandler});
// //处理最后一个Promise的时候,队列resolve推入一个空函数,不形成影响,不会报错----若是没有,则会报错
// };
return this.then(undefined, errHandler);
}
finally(finalHandler) {
return this.then(finalHandler, finalHandler);
}
};
复制代码
测试用例:
7 catch测试
16 finally测试——异步代码错误
17 finally测试——同步代码错误
目前而言,咱们的catch
还不具有捕获代码报错的能力。思考,错误的代码来自于哪里?确定是使用者的代码,2个来源分别有:
MyPromise
对象构造函数回调then
方法的2个回调 捕获代码运行错误的方法是原生的try...catch...
,因此我用它来包裹这些回调运行,捕获到的错误进行相应处理。为确保代码清晰,提取了
resolver
、rejecter
两个函数,由于是es5写法,须要手动处理this
指向问题
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//定义function须要手动处理this指向问题
let _this = this; //++
function resolver(...arg) { //++
_this.__succ_res = arg;
_this.status = 'success';
_this.__queue.forEach(json => {
json.resolve(...arg);
});
};
function rejecter(...arg) { //++
_this.__err_res = arg;
_this.status = 'error';
_this.__queue.forEach(json => {
json.reject(...arg);
});
};
try { //++
fn(resolver, rejecter); //-+
} catch(err) { //++
this.__err_res = [err];
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...err);
});
};
}
then(onFulfilled, onRejected) {
//箭头函数绑定了this,若是使用es5写法,须要定义一个替代的this
return new MyPromise((resFn, rejFn) => {
function handle(value) {
//then方法的onFulfilled有return时,使用return的值,没有则使用回调函数resolve的值
let returnVal = value; //-+
if (onFulfilled instanceof Function) { //-+
try { //++
returnVal = onFulfilled(value);
} catch(err) { //++
//代码错误处理
rejFn(err);
return;
}
};
if (returnVal && returnVal['then'] instanceof Function) {
//若是onFulfilled返回的是新Promise对象,则调用它的then方法
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
resFn(returnVal);
};
};
function errBack(reason) {
//若是有onRejected回调,执行一遍
if (onRejected instanceof Function) {
try { //++
let returnVal = onRejected(reason);
//执行onRejected回调有返回,判断是否thenable对象
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(res => {
resFn(res);
}, err => {
rejFn(err);
});
} else {
//不是thenable的,直接丢给新对象resFn回调
resFn(returnVal);
};
} catch(err) { //++
//代码错误处理
rejFn(err);
return;
}
} else {//传给下一个reject回调
rejFn(reason);
};
};
if (this.status === 'success') {
handle(...this.__succ_res);
} else if (this.status === 'error') {
errBack(...this.__err_res);
} else {
this.__queue.push({resolve: handle, reject: errBack});
};
})
}
};
复制代码
测试用例:
11 catch测试——代码错误捕获
12 catch测试——代码错误捕获(异步)
13 catch测试——then回调代码错误捕获
14 catch测试——代码错误catch捕获
其中第12个异步代码错误测试,结果显示是直接报错,没有捕获错误,原生的Promise
也是这样的,我有点不能理解为啥不捕获处理它。
这是Promise
的一个关键特性,处理起来不难,在执行回调时加入状态判断,若是已是成功或者失败状态,则不运行回调代码。
class MyPromise {
constructor(fn) {
this.__succ_res = null; //保存成功的返回结果
this.__err_res = null; //保存失败的返回结果
this.status = 'pending'; //标记处理的状态
this.__queue = []; //事件队列
//箭头函数绑定了this,若是使用es5写法,须要定义一个替代的this
let _this = this;
function resolver(...arg) {
if (_this.status === 'pending') { //++
//若是状态已经改变,再也不执行本代码
_this.__succ_res = arg;
_this.status = 'success';
_this.__queue.forEach(json => {
json.resolve(...arg);
});
};
};
function rejecter(...arg) {
if (_this.status === 'pending') { //++
//若是状态已经改变,再也不执行本代码
_this.__err_res = arg;
_this.status = 'error';
_this.__queue.forEach(json => {
json.reject(...arg);
});
};
};
try {
fn(resolver, rejecter);
} catch(err) {
this.__err_res = [err];
this.status = 'error';
this.__queue.forEach(json => {
json.reject(...err);
});
};
}
//...略
};
复制代码
测试用例:
27 Promise状态屡次改变
到这里为止,若是执行下面一段代码,
function test30() {
function fn30(resolve, reject) {
console.log('running fn30');
resolve('resolve @fn30')
};
console.log('start');
let p = new MyPromise(fn30);
p.then(res => {
console.log(res);
}).catch(err => {
console.log('err=', err);
});
console.log('end');
};
复制代码
输出结果是:
//MyPromise结果
// start
// running fn30
// resolve @fn30
// end
//原生Promise结果:
// start
// running fn30
// end
// resolve @fn30
复制代码
两个结果不同,由于onFulfilled 和 onRejected 方法不是异步执行的,须要作如下处理,将它们的代码放到本轮任务队列的末尾执行。
function MyPromise(callback) {
//略……
var _this = this;
function resolver(res) {
setTimeout(() => { //++ 利用setTimeout调整任务执行队列
if (_this.status === PENDING) {
_this.status = FULFILLED;
_this.__succ__res = res;
_this.__queue.forEach(item => {
item.resolve(res);
});
};
}, 0);
};
function rejecter(rej) {
setTimeout(() => { //++
if (_this.status === PENDING) {
_this.status = REJECTED;
_this.__err__res = rej;
_this.__queue.forEach(item => {
item.reject(rej);
});
};
}, 0);
};
//略……
};
复制代码
测试用例:
30 then方法的异步执行
以上,是我全部的代码书写思路、过程。完整代码与测试代码到github下载