- MyPromise.constructor
初始状态.pngnew Promise((resolve, reject) => { reject('my reason'); });
首先MyPromise类接收一个函数作为入参,在此称为executor
MyPromise有三个状态:pending、fulfilled、rejected,初始状态为pending
我们初步得到以下代码:
成功和失败处理程序.png立即执行传入的executor,为executor传入handleResolve和handleReject来分别处理成功和失败的情况, 等待executor来主动执行某个handler,来通知该Promise对象应该变更为fulfilled还是rejected。
更改状态,保存value或reason.pnghandleResolve和handleReject都接收一个参数,我们将handleResolve的参数作为该promise对象的value属性值,将handleReject的参数作为该promise对象的reason属性值。因为状态一旦改变就会凝固,所以一个promise只会有一个value或一个reason
状态改变后执行注册过的成功或失败的回调.pnga = new Promise(() => { });
a.then(function onSuccess1() { });
a.then(function onSuccess2() { });
a.catch(function onFail1() { });
a.catch(function onFail2() { });
调用者可以通过then和catch方法为promise对象注册多个成功回调和失败回调,等待状态改变时执行;但注册这些回调时很有可能a仍处于pending状态,所以应先保存这些回调,在状态变更时执行。
a = new Promise(() => {
throw new Error('')
});
a.catch(function handleErr(reason) {
console.log('catch error', reason);
});
image.png上面的promise a,当执行executor报错时,状态应变更为rejected,同时错误被下面的handleErr捕获;
但是,当我们catch到executor()的错误后立即执行handleReject方法,会由于failCallbacks数组为空而抛出异常,因为此时还没有执行后面的a.catch,这个处理错误的回调handleErr还没有被加入到failCallbacks里。但实际这个错误是应该被handleErr所捕获的。
1、虽然在执行executor时,可能会调用handleResolve并因此触发执行successCallbacks里的回调,也许这些回调里也会抛出错误,但不会被这里的try-catch捕获,因为这些回调是异步的;这类错误会在那些callbacks里面处理,后面会提到。
2、在调用者执行了handleResolve或handleReject之后, 即使代码报错进入handleReject的逻辑,也会由于【Promise对象的状态已变更】而被忽略,尝试执行以下代码:
// resolve后报错
a = new Promise((resolve) => {
resolve();
throw new Error('')
});
a.then(function () {
console.log('my state is fulfilled');
});
a.catch(function handleErr(reason) {
console.log('catch error', reason);
});
// reject后报错
b = new Promise((resolve, reject) => {
reject('reject Error');
throw new Error('throw Error')
});
b.catch(function handleErr(reason) {
console.log('catch error', reason);
});
TODO:如果传给handleResolve或者handleReject的值是一个MyPromise对象:
——————我是明显的分隔线——————————————————————————————————————
——————我是明显的分隔线——————————————————————————————————————
- MyPromise.prototype.then
1、then方法会返回一个新的Promise对象,所以才能进行链式调用
2、then方法的第一个入参onfulfilled永远是【调用这个then方法的promise对象】的成功回调
例如:
a = new Promise();
a.then(function success1() {
// 这里a其实只有一个成功回调,就是第一个then的入参success1
}).then(function success2() {
// 第2个then的入参success2其实是【a调用then方法返回的新的promise对象】的成功回调
})
b = new Promise(() => {
}).then(function success1() {
// 这里success1是前一个promise的成功回调
});
// 这里b指的是第一个promise调用了then方法后返回的新的promise对象
b.then(function success2() {
// 这里success2是b的成功回调
})
开始实现:
image.png1、then方法接收2个参数,promise状态变为fulfilled则执行onfulfilled,变为rejected执行onrejected
2、调用then方法返回的新的 promise (暂时称为 resultPromise )对象要实现:
[1] 当【当前promise对象】状态变为 fulfilled,但onfulfilled不是一个函数时,这个resultPromise的状态也应该变成fulfilled,value设置为当前promise对象的value(value为引用型对象时指向的也是同一个对象)
[2] 当【当前promise对象】状态变为 rejected,但onrejected不是函数时,resultPromise的状态也应该变成rejected,reason设置为当前promise对象的reason
[3] 如果 onfulfilled 或 onrejected 正常执行结束(无论是立即执行还是状态改变时批量执行)(返回执行结果XX),那么resultPromise的状态都应该变为fulfilled,value为XX
[4] 如果 onfulfilled 或 onrejected 执行时抛出异常,那么resultPromise的状态应该变为rejected,reason为错误对象
image.png当执行.then方法时:
[1] 如果此promise为pending状态,那么onfulfilled, onrejected应该被加入callbacks里,等待状态变更后执行;
[2] 如果状态是fulfilled,将value作为入参传给onfulfilled方法,即需要执行 onfulfilled(this.value)
[3] 如果状态是rejected,将reason作为入参传给onrejected方法,即需要执行 onrejected(this.reason)
利用getFnHandleStateChange方法生成两个函数.png由于执行then方法返回的resultPromise对象的状态,取决于onfulfilled和onrejected的执行结果(上面说的第三点和第四点),也就是说,resultPromise的状态要等到onfulfilled或onrejected执行完后才能确定:
所以现在出现了一个新的需求:在onfulfilled / onrejected执行完后,需要根据执行情况来决定执行resolve还是reject,来更改resultPromise的状态。
虽然在promise状态已经变更的情况下,立即执行onfulfilled / onrejected,就能更改resultPromise的状态,但如果是pending状态,onfulfilled / onrejected都需要等待执行,那么到时我们需要执行的就不光是这两个回调了,还有【更改resultPromise状态】的这步操作。所以我们将这2步操作放在一个函数里,push到callbacks数组里等待执行:
我们不光根据onfulfilled / onrejected的执行情况决定resultPromise的状态,也根据【执行后的返回值】来设置resultPromise的value或reason。接下来补充getFnHandleStateChange方法的内部处理:
在此之前,我们再来重复一遍onfulfilled / onrejected 的执行是如何影响resultPromise的状态的:
【1】如果 onfulfilled 或 onrejected 正常执行结束(返回执行结果为XX),那么resultPromise的状态都应该变为fulfilled,value为XX
*********这里有一个特殊情况********
如果返回结果XX也是一个promise对象,那么 resultPromise 要保持pending状态直到 XX 的状态发生变化:
a. 当 XX 的状态变成fulfilled,那么resultPromise也变成fulfilled,resultPromise的value设置为val的value
b. 当 XX 的状态变成rejected,那么resultPromise也变成rejected,resultPromise的reason设置为val的reason
【2】如果 onfulfilled 或 onrejected 执行时抛出异常,那么resultPromise的状态应该变为rejected,reason为错误对象
image.png
接下来我们补充一下catch方法,并初步使用一下MyPromise:
catch.png
情况1:链式调用
a = new MyPromise(resolve => {
setTimeout(() => {
resolve(100);
}, 1000);
}) // 称为promise1
.then(res1 => { // 执行then时promise1为pending状态,将回调加入promise1的successCallbacks里
console.log('res1', res1);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(200)
}, 1000);
});
}) // 返回一个promise2,状态为pending状态,并等待promise1状态改变
.then(res2 => { // 执行then时promise2为pending状态,将回调加入promise2的successCallbacks里
console.log('res2', res2);
return 300;
}) // 返回一个promise3,状态为pending状态,并等待promise2状态改变
.catch(err => { // 执行catch时promise3为pending状态,将回调加入promise3的failCallbacks里
console.log(err);
}); // 返回一个promise4,状态为pending状态,并等待promise3状态改变
》promise1状态变为fulfilled,value为100,执行successCallbacks(1个回调),返回了一个promise对象A
》等A状态变为rejected,reason为200,那么执行A的failCallbacks里的回调(1个,入参为A.reason),回调里抛出了A.reason这个错误,并被catch捕获,将A.reason传递给promise2
》使得 promise2状态变更为rejected,reason为200,执行promise2的failCallbacks里的回调,我们没有显式为它传入onrejected,但then内部自动包装了一个onRejected并加入了failCallbacks,这时执行这个回调(入参为promise2.reason),里抛出了一个异常值200,被catch捕获,将200传递给promise3
》使得promise3的状态变更为rejected,reason为200,执行promise3的failCallbacks里的回调,正常执行结束,使得promise4状态变更为fulfilled,value为undefined
情况2:多次注册回调
正经promise.png
咱的promise.png
这时似乎有些不对劲,执行顺序和正经promise是不一样的:
》我们的MyPromise对象在执行完第一个failCallback后,promise1(指a第一次执行catch时返回的那个promise对象)的状态变更为fulfilled,并且立即执行了promise1的成功回调,输出了 【C 100】,b状态变更成了fulfilled,然后才开始执行了第二个failCallback,如下图里的打印信息:
image.png
但我们知道正常promise的回调都是异步的,在执行第二个failCallback时b的状态:
企业微信截图_16408494134388.png
加个setTimeout行不行.png 顺序正常了.png接下来让我们把回调改成异步的:
这时候已经能满足大部分需求了,TODO:处理异常的result返回值
还有区别:宏任务与微任务
new Promise(resolve => {
num.innerHTML = Math.random();
resolve();
}).then(() => {
longtime(); num.innerHTML = Math.random();
}).then(() => {
longtime(); num.innerHTML = Math.random();
}).then(() => {
longtime(); num.innerHTML = Math.random();
})
promise.gif
MyPromise回调每次都会更新UI.gif
执行到.then时
1、如果这个promise状态已经是fulfilled
那么入参回调加入微任务队列,无论之后的settimeout时间设置成多少,都会先执行then的回调(case1)
2、如果状态是pending
入参回调会保存到数组中,等待resolve的时候进行调用,调用时加入微任务队列。此时then的入参回调先执行还是setTimeout的回调先执行取决于(执行resolve的时间节点和settimeout中设置的时长)
|__假如先resolve(进入微任务),定时器后到期(进入宏任务),则先执行then的回调再执行setTimeout的回调(case2)
|__ 假如定时器先到期(进入宏任务),后resolve(进入微任务),这时谁先执行取决于加入队列时队列的情况
|__ 如果定时器到期时,宏任务队列是空的
|__ 微任务队列也是空的,则先settimeout再then的回调(case3)
|__ 微任务队列里有任务,则先执行微任务队列中的任务,
|__只要promise能在执行settimeout这个宏任务之前resolve,则先then,否则先settimeout(case4/case5)
|__ 如果定时器到期时,宏任务队列有任务(无所谓微任务队列里面有没有任务)
|__只要promise能在执行settimeout这个宏任务之前resolve,则先then,否则先settimeout(case6/case7)
就是如果then的回调一进入微任务队列,settimeout的回调就没机会了,肯定后执行
如果settimeout的回调先进入宏任务队列,那么then的回调还有机会先执行,只要在执行这个宏任务之前resolve,就还是它先执行
网友评论