当前位置: 首页 > 科技观察

关于Promise的执行顺序

时间:2023-03-19 00:04:50 科技观察

最近看到一段很有意思的Promise相关代码:newPromise((resolve)=>{console.log(1)resolve()}).then(()=>{newPromise((resolve)=>{console.log(2)resolve()}).then(()=>{console.log(4)})})。then(()=>{console.log(3)})第一次看到这段代码的时候,我以为输出会是:1,2,3,4,结果被实际打脸了输出。如图,实际输出结果为:1,2,4,3。代码分析为了搞清楚为什么实际输出的是:1,2,4,3,我们来一步步分析代码执行。我们知道,当Promise被实例化时,传入的回调会被立即执行,Promise的then回调会被放入microtask队列中,等待执行。队列是一个先进先出的列表,先进入队列的回调会先执行。前面的代码中,一共有5个回调函数。Callback1是Promise实例化时的回调,所以会立即执行。这时控制台打印出数字1,然后调用resolve()方法。此时,Promise状态变为fulfilled(如果没有调用resolve()方法,则Promise状态为pending)。Promise实例化后,第一个then()方法被调用,回调2会被放入microtask队列,等待执行。then方法什么时候调用?这时候疑点就来了。第一个then()方法调用后,第二个then方法会立即被调用吗?如果是这样,输出结果应该是:1,2,3,4。显然,此时不会立即调用第二个then()方法,即不会立即将回调5放入微任务队列中。如果没有,什么时候调用?这时候就需要看Promise/A+规范了。重点如下:2.2then方法promise的then方法接受两个参数:promise.then(onFulfilled,onRejected)2.2.2如果onFulfilled是一个函数:2.2.2.1当promise处于processed状态时,函数必须被调用并将promise的值作为第一个参数。2.2.2.2在promise被resolve之前,不得调用该函数。2.2.2.3函数调用次数不超过一次。2.2.6然后可以在同一个承诺上多次调用。2.2.6.1如果一个promise被解决,所有相应的onFulfilled回调必须按照它们被组织到then的顺序依次调用。2.2.6.2如果一个承诺被拒绝,所有相应的onRejected回调必须按照它们被组织到then的顺序依次调用。2.2.7然后必须返回一个promise。promise1=newPromise(resolve=>resolve())//promise1可以多次调用then//onFulfilled回调的执行顺序按照.then的调用顺序执行。promise1.then(onFulfilled1)//1promise1.then(onFulfilled2)//2promise1.then(onFulfilled3)//3//上面3个onFulfilled,按照1,2,3的顺序执行//调用.then方法后,返回一个新的promisepromise2=promise1.then(onFulfilled,onRejected);综上所述,第一个then()方法调用后,会返回一个新的Promise。这样做的目的是保持链式调用,then()方法中的onFulfilled回调会等待Promise状态修改后再调用。我们稍微修改一下之前代码的调用形式,如下:constp1=newPromise((resolve)=>{console.log(1)resolve()})constp2=p1.then(()=>{newPromise((resolve)=>{console.log(2)resolve()}).then(()=>{console.log(4)})})constp3=p2.then(()=>{console.log(3)})p1.then()会返回一个新的名为p2的Promise,p2.then()的回调会在p1.then()中的回调函数执行完毕后被调用,即p2之后这个承诺的状态已经改变。所以p2.then()只有在回调2执行完毕后才会执行。我们再看一下回调2的内容。回调2首先实例化一个Promise。实例化的回调是回调3,会立即执行。这时控制台打印出数字2,然后调用resolve()方法,此时修改了Promise的状态。变为fulfilled,后续回调4将被放入microtask队列。callback2执行完后,执行p2.then(),将callback5放入微任务队列。按照队列先进先出的执行顺序,先执行回调4,再执行回调5,所以控制台会先输出数字4,再输出数字3。如果要输出结果:1,2,3,4,可以将代码改成如下形式:constp1=newPromise((resolve)=>{console.log(1)resolve()})p1.then(()=>{newPromise((resolve)=>{console.log(2)resolve()}).then(()=>{console.log(4)})})p1.then(()=>{console.log(3)})按照前面2.2.6的规则,then可以在同一个promise上调用多次,然后p1之后会直接按照他们的放入微任务队列调用命令。