Promise

作者: 欢西西西 | 来源:发表于2021-12-15 19:23 被阅读0次
  • MyPromise.constructor

new Promise((resolve, reject) => { reject('my reason'); });
首先MyPromise类接收一个函数作为入参,在此称为executor
MyPromise有三个状态:pending、fulfilled、rejected,初始状态为pending
我们初步得到以下代码:

初始状态.png

立即执行传入的executor,为executor传入handleResolve和handleReject来分别处理成功和失败的情况, 等待executor来主动执行某个handler,来通知该Promise对象应该变更为fulfilled还是rejected。

成功和失败处理程序.png

handleResolve和handleReject都接收一个参数,我们将handleResolve的参数作为该promise对象的value属性值,将handleReject的参数作为该promise对象的reason属性值。因为状态一旦改变就会凝固,所以一个promise只会有一个value或一个reason

更改状态,保存value或reason.png

a = new Promise(() => { });
a.then(function onSuccess1() { });
a.then(function onSuccess2() { });
a.catch(function onFail1() { });
a.catch(function onFail2() { });
调用者可以通过then和catch方法为promise对象注册多个成功回调和失败回调,等待状态改变时执行;但注册这些回调时很有可能a仍处于pending状态,所以应先保存这些回调,在状态变更时执行。

状态改变后执行注册过的成功或失败的回调.png
a = new Promise(() => {
    throw new Error('')
});
a.catch(function handleErr(reason) {
    console.log('catch error', reason);
});

上面的promise a,当执行executor报错时,状态应变更为rejected,同时错误被下面的handleErr捕获;
但是,当我们catch到executor()的错误后立即执行handleReject方法,会由于failCallbacks数组为空而抛出异常,因为此时还没有执行后面的a.catch,这个处理错误的回调handleErr还没有被加入到failCallbacks里。但实际这个错误是应该被handleErr所捕获的。

image.png

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的成功回调
})

开始实现:

1、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)

image.png

由于执行then方法返回的resultPromise对象的状态,取决于onfulfilled和onrejected的执行结果(上面说的第三点和第四点),也就是说,resultPromise的状态要等到onfulfilled或onrejected执行完后才能确定:
所以现在出现了一个新的需求:在onfulfilled / onrejected执行完后,需要根据执行情况来决定执行resolve还是reject,来更改resultPromise的状态。
虽然在promise状态已经变更的情况下,立即执行onfulfilled / onrejected,就能更改resultPromise的状态,但如果是pending状态,onfulfilled / onrejected都需要等待执行,那么到时我们需要执行的就不光是这两个回调了,还有【更改resultPromise状态】的这步操作。所以我们将这2步操作放在一个函数里,push到callbacks数组里等待执行:

利用getFnHandleStateChange方法生成两个函数.png

我们不光根据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,就还是它先执行

相关文章

网友评论

      本文标题:Promise

      本文链接:https://www.haomeiwen.com/subject/qvqpfrtx.html