再也不怕问Promise执行顺序,拿捏

149 阅读8分钟

轻松拿捏Prmise的执行顺序,深入理解规则

看完这篇文章,再也不怕问你Promise执行顺序啦!

1.嵌套的Promise

1.1无返回值的Promise

我们先来看一下下面的代码,看看执行顺序是什么

        let p1 = new Promise((resolve, reject) => {
            //Promise1
            console.log('---1');
            resolve(1);
        }).then(() => {
            //回调 A
            console.log('---2');
            new Promise((resolve, reject) => {
                //Promise2
                console.log('---4')
                resolve(2);
            }).then(() => {
                //回调C
                console.log('---5');
            }).then(() => {
                //回调D
                console.log('---6');

            })
        }).then(() => {
            //回调B
            console.log('---3');
        })

公布答案:

1 2 4 5 3 6

为了更好的分析,读者可以先读一下法则(笔者根据规范总结)和一些定义。

前置定义处理回调的方法意思是:运行该回调并对回调的返回值进行分析处理,下面称为handler

法则1!!微任务队列不能立刻执行,得等待系统调度执行!!(有关宏任务,微任务知识的本文默认有该基础)

法则2:我们这么想象每个promise有两个数组,一个onFulfilledCallbacks(成功的回调),一个onRejectCallbacks(失败时的回调);当promise状态改变时,如果resolve,状态变更为fulfilled,将遍历onFulfilledCallbacks数组,把里面的handler推入微任务队列中,如果reject,状态变更为rejected.则将遍历onRejectCallbacks数组,把里面的handler推入微任务队列中

法则3:当遇到then时,会先检查当前产生这个then的promise的状态,如果状态为pending,则将then(onFulfilled,onRejected)的入参onFulfilledonRejected裹挟在处理方法handler里面,分别push进Promise自身内存空间onFulfilledCallbacksonRejectCallbacks中;如果状态为fulfilled或者rejected,则直接将处理该回调onFulfilled或者onRejected的方法handler推进微任务队列中。 PS注意,别混淆,!!then里面的回调是在微任务队列运行的,但解析该then函数是直接依次直接解析的!!

法则4:then链式调用时,必须等上一个then内部的promise状态改变(运行完then的回调函数得到结果x如果x不为promsie实例,则状态变更为resolved或rejected;如果x为promise实例,则递归等待X状态改变 ,这也是handler的逻辑) 才能继续执行,同时必须返回一个Promsie。章节1.2会细讲。

好的,法则列出完毕。做对了吗?话不多说,下面笔者展示一下分析过程 首先代码遇到new Promise,根据MDN文档描述,

executor 是同步调用的(在构造 Promise 时立即调用),并将 resolveFunc 和 rejectFunc 函数作为传入参数。

所以直接运行里面的构造函数的参数executor,也就是(resolve,reject)=>{....},就先执行console.log('---1')。 接着我们看到直接执行了resolve(1),我们知道当执行resolve()函数时,所在的Promise状态将改变为已解决(resolved),也就是从pending变成fulfilled,根据法则2,遍历自己的onFulfilledCallbacks,这时发现里面没东西,于是过。这里有些同学就有疑问了,后面的then不是回调吗,怎么不执行,哎哎哎,童鞋你先别急,我代码都还在resolve(1)这里,都没运行到后面的then呢,当然为空数组; 接着终于来到第一个then了,我们暂且称为回调A,根据法则3,发现产生该then的promise为Promise1,它的状态已经resolved变成fulfilled直接将处理该回调A(也就是中间那一坨)的方法handlerA推入微任务队列,等待执行;接着代码来到下一个then,也就是回调B,根据法则3,检查产生该then的promsie也就是回调A所在的then返回的Promise,暂为PromiseA(法则4:then须返回promise)的状态,首先它是没有自定义返回值的( 自己没有写return,只要不return promise实例就行 ),那么现在有返回值了吗?答案是没有,因为法则4说需要等待then里面回调的结果(此处指回调A)才判断状态改变,很明显,handlerA还在微任务队列里面,都没运行,所以此时状态的pending,将handlerB推入PromiseA自身的onFulfilledCallbacks中,等待handlerA执行完再加入微任务队列。 好,现在从头到尾解析完了,没啥事了,然后系统就查看微任务队列,发现里面有个handlerA,刷刷执行,就到了console.log('---2'),接着发现new Promsie,暂时称为promise2,然后直接执行executor,就console.log('---4'),接着遇到resolve(2),然后该promsie2的状态变更为fulfulled,根据法则2,遍历自己(promise2)的onFulfilledCallbacks,这时发现里面没东西,继续!就执行到回调所在的then,根据法则3,发现promsie2的状态为fulfilled直接将该处理回调C的方法handlerC推入微任务队列,等待执行,接着碰到回调D所在的then,那就看回调C所在的then返回的promsie(暂为promiseC)状态是否改变,同理根据法则4handlerC还在微任务队列中没运行,说明决定处理回调D的方法handlerD推入微任务时机的promise状态为pending,将handlerD推入promiseC的onFulfilledCallbacks中,等待promsieC状态改变。 分析一下场上状态。在Promise内存中,在PromiseA的内存队列onFulfilledCallbacks存放着handlerB,在PromiseC的onFulfilledCallbacks存放着handlerD;在微任务队列中,存在着handlerC状态图

此时回调A总算运行完啦,返回值为undefined,根据法则4,返回值xundefined,则PromsieA状态变更为fulfilled,根据法则2,遍历自身缓存队列将handlerB推入微任务队列中,这时候微任务队列存在 handlerChandlerB。接着系统发现没啥事了,遍历微任务队列依次执行,先执行handlerCconsole.log('---5'),执行完C返回值undefined,在handlerC内部将PromiseC状态变更为fulfulled,接着遍历自身缓存队列将handlerD推入微任务队列中;接着执行微任务handlerBconsole.log('---3');然后执行微任务handlerDconsole.log('---6'),至此结束。

1.2 有return的Promsie

我们对上面代码稍微做一下改造,在回调A中加个return,上述代码顺序是否会改变呢?

        let p1 = new Promise((resolve, reject) => {
            //Promise1
            console.log('---1');
            resolve(1);
        }).then(() => {
            //回调 A
            console.log('---2');
            return new Promise((resolve, reject) => {
                //Promise2
                console.log('---4')
                resolve(2);
            }).then(() => {
                //回调C
                console.log('---5');
            }).then(() => {
                //回调D
                console.log('---6');

            })
        }).then(() => {
            //回调B
            console.log('---3');
        })

公布结果:变了,

1 2 4 5 6 3

有童鞋好奇,加了个return ,为什么3会最后才执行;这一切都是法则4的魔力,根据promiseA+规范 2.2.7,

then 方法必须返回一个 promise 对象,promise2 = promise1.then(onFulfilled, onRejected); 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 promise 解决过程:[[Resolve]](promise2, x)

和规范2.3.1与2.3.2

2.3.1 如果 x 为 promise ,则使 promise 接受 x 的状态 2.3.2 如果 x 为 promise ,则使 promise 接受 x 的状态: 2.3.2.1如果 x 处于等待态, promise 需保持为等待态直至 x 被完成或拒绝 2.3.2.2如果 x 处于完成态,用相同的值完成 promise 2.3.2.3如果 x 处于拒绝态,用相同的拒绝原因拒绝 promise

" ---首先解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来完成 promise " 可以这么理解该条,如果返回值x是Promise实例,则该then返回的Promise的状态必须等待x的状态变化,两者状态同步,你等待我也等待,你fulfilled我也fulfulled,你拒绝我也拒绝。如果promise嵌套再嵌套呢,这就需要一直递归等待x状态改变。想象这个then返回的new Promise(resolve,reject)里面这两个resolve和reject一直往下递归传,直到最后一个处理,promise的状态与x的状态同步。先看看下面代码,pp2的状态和谁的状态相同呢?

       let pp1 = new Promise((resolve, reject) => {

           resolve();
       })
       let pp2 = pp1.then(() => {
           return new Promise((resolve, reject) => {
               resolve();
           }).then(() => {
               //A
               return new Promise((resolve, reject) => {
                   resolve();
               }).then(() => {
                   //B
               })
           })
       })

就像这里的代码,嵌套再嵌套,遇到return,根据promsie解析规则,pp2状态先与A所在的then返回的Promise(称为promiseA)状态一致,当运行A时,发现还有返回值,那么PromiseA的状态又和B返回的Pormise状态(PromsieB)一致,因此pp2,PromiseA,PromiseB状态是一致的。相当于一直把pp2的resolve这个函数往下传,实现同步。

有了上面基础,那么分析1.2开头的代码顺序就简单了很多,先打印 ---1 ,---2没问题,然后遇到了return,那么回调A所在的then返回的Promise(称为PromiseA)状态与谁一致那,联合法则三的PS: !!then里面的回调是在微任务队列运行的,但解析该then函数是直接依次直接解析的!!,那么肯定是最深处的那个then返回的Promise(称为PromiseD),那么显然回调B得等A所在的then返回的Promise (PromiseA)状态改变再执行,然后你得执行完回调D才能改变状态,自然回调B再最后调用,输出

1 2 4 5 6 3

好,看完这里,有没有更深刻?希望多多关注笔者~

2 .实战

2.1 多层嵌套

        let pp1 = new Promise((resolve, reject) => {
            console.log('---1')
            resolve();
        })
        let pp2 = pp1.then(() => {
            console.log('---2');

            return new Promise((resolve, reject) => {
                console.log('---3');

                resolve();
            }).then(() => {
                console.log('---4');

                return new Promise((resolve, reject) => {
                    resolve();
                    console.log('---5');

                }).then(() => {
                    console.log('--6');

                })
            })
        }).then(() => {
            console.log('--7');

        })

2.2 并列的promise

        let pp3 = new Promise((resolve, reject) => {
            console.log('---1')
            resolve();
        }).then(() => {
            console.log('--2');

        }).then(() => {
            console.log('--3');

        })
        let pp4 = new Promise((resolve, reject) => {
            console.log('---4')
            resolve();
        }).then(() => {
            console.log('--5');

        }).then(() => {
            console.log('--6');

        })

2.3 公布答案

代码2.1执行顺序为 :

1 2 3 4 5 6 7

代码2.2执行顺序为:

1 4 2 5 3 6

最后,掌握这几种法则,promise轻松拿捏