这是一个关于Promise 微任务的题目,猜猜以下代码输出是什么?为什么?
let p1 = Promise.resolve() .then(function f1(v) { console.log(1) }) .then(function f2(v) { console.log(2) }) .then(function f3(v) { console.log(3) }) p1.then(function f4(v) { console.log(4) }) p1.then(function f5(v) { console.log(5) }) let p2 = Promise.resolve() .then(function f11(v) { console.log(11) }) .then(function f22(v) { console.log(22) }) .then(function f33(v) { console.log(33) }) p2.then(function f44(v) { console.log(44) }) p2.then(function f55(v) { console.log(55) })
有的小伙伴会认为输出的结果是 1, 2, 3, 4, 5, 11, 22, 33, 44, 55。其理论依据是“Promise的then属于微任务,代码在执行的时候,遇到.then(fn), 会把fn加入到微任务队列”,所以这些then里的回调函数被依次加入微任务队列,到执行队列任务时就会依次输出。
有的小伙伴会认为输出的结果是 1、11、2、22、3、33、4、44、5、55。理由是“Promise的then属于微任务,但只有当前一个Promise对象的resolve,才会触发让其后的.then(fn)中的fn加入微任务队列”。所以一开始微任务队列是[f1, f11], 执行f1时(return的是undefined,)导致当前promise对象resovle(undefined),从而把f2加微任务队列[f11, f2],f11执行时同样把f22加任务队列[f2, f22],一次轮流执行。
两种说法都没问题,问题在于不够严谨,不能描述当前这个例子,导致得出错误的推断结果。更严谨的说法是:
//code 1 let p = new Promise(function f1(resolve) { setTimeout(function f2(){ resolve(2) console.log(1) }, 1000) }) p.then(function f3(v) { console.log(v) })
在上例中,f1是同步执行的代码,在执行时创建一个定时器(f2会加入宏队列),之后执行 p.then,此时 f3并没有立即加入微任务队列。 1秒后 f2执行时,运行 resolve(1),此时才触发f3加微任务队列。 详细的流程是
//code 2 let p1 = new Promise(function f1(resolve1) { setTimeout(resolve1) }) let p2 = p1.then(function f2(v) { console.log(2) }) let p3 = p2.then(function f3(v) { console.log(3) }) let p11 = new Promise(function f11(resolve2) { setTimeout(resolve2) }) let p22 = p11.then(function f22(v) { console.log(22) }) let p33 = p22.then(function f33(v) { console.log(33) })
对于Promise我们需要知道,链式调用.then之后会返回一个新的Promise对象。以上代码的执行流程是
最终输出顺序为 2、3、22、33。以上代码等价于常见链式写法
new Promise( resolve => setTimeout(resolve) ) .then( v => console.log(2) ) .then( v => console.log(3) ) new Promise( resolve => setTimeout(resolve) ) .then( v => console.log(22) ) .then( v => console.log(33) )
此时一切还符合我们的预期。再看下一条规则:
// code 3 let p1 = Promise.resolve(1) let p2 = p1.then(function f2() { console.log(2) }) let p3 = p2.then(function f3() { console.log(3) }) let p11 = new Promise(function f11(resolve) { resolve(11) }) let p22 = p11.then(function f22() { console.log(22) }) let p33 = p22.then(function f33() { console.log(33) })
看起来和 code 2 代码类似,在实际执行的时候会发现得到截然不同的结果。分析一下具体执行流程:
先执行同步代码。
扫描微队列
最终输出结果为 2、22、3、33。以上代码等价于常见链式写法
// code 4 Promise.resolve(1) .then(() => console.log(2)) .then(() => console.log(3)) new Promise(resolve => resolve()) .then(() => console.log(22)) .then(() => console.log(33))
回到开头的例子
let p1 = Promise.resolve() //1 .then(function f1(v) { console.log(1) }) //2 .then(function f2(v) { console.log(2) }) //3 .then(function f3(v) { console.log(3) }) //4 p1.then(function f4(v) { console.log(4) }) //5 p1.then(function f5(v) { console.log(5) }) //6 let p2 = Promise.resolve() //7 .then(function f11(v) { console.log(11) }) //8 .then(function f22(v) { console.log(22) }) //9 .then(function f33(v) { console.log(33) }) //10 p2.then(function f44(v) { console.log(44) }) //11 p2.then(function f55(v) { console.log(55) }) //12
p1是第4行then得到的Promise对象, p2是第10行then得到的对象。一开始,代码按照code4 的逻辑执行。依次输出 1、11、2、22、3(运行f3)、33(运行f33)。当运行到f3后,p1 被resolve,导致f4、f5被同时加入微队列。当运行到f33时,p2被resolve,导致f44、f55被同时加入微队列。最后微队列里为[f4, f5, f44, f55],所以最后依次输出4、5、44、55
到目前为止,你应该真正理解了宏任务、微任务、以及Promise。
推一波我的试学营,适合新手,2周时间学习HTML、CSS、基础到JavaScript,从0开始做一个酷炫的音乐播放器,这篇文章的读者可能不需要,但身边想学前端的朋友、师弟师妹真的需要。
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com