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