首先阐述一下两者
在Promise中,我们可以将一个异步任务封装在一个Promise对象中,并为其绑定resolve和reject回调函数。当异步任务执行成功时,调用resolve,失败时则调用reject。Promise对象的状态有三种:pending(进行中),fulfilled(已完成,相当于resolve),rejected(已失败,相当于reject)。当Promise状态变为fulfilled或rejected时,Promise会将对应的回调函数加入到任务队列中等待执行。
setTimeout用于设定一个定时器,在定时器到达设定的时间后,将目标函数加入到任务队列中等待执行。由于JavaScript是单线程执行,当主线程执行完当前所有任务后才会去任务队列中取出任务执行。因此,setTimeout中设定的回调函数并不是在设定的时间到达时马上执行,而是在主线程中的所有任务执行完毕后才会被调用。另外,值得注意的是,当setTimeout的时间设为0时,相当于直接将目标函数加入到了任务队列中,但它的执行顺序也会受到当前主线程任务的影响。
可以看到Promise和setTimeout都会将函数加入任务队列,那么执行顺序应该是怎么样的?
JavaScript通过任务队列管理所有异步任务,而任务队列还可以细分为MacroTask Queue和MicoTask Queue两类:
MacroTask Queue
MacroTask Queue(宏任务队列)主要包括setTimeout, setInterval, setImmediate, requestAnimationFrame, UI rendeing, NodeJS中的I/O等。
MicroTask Queue
MicroTask Queue(微任务队列)主要包括两类:
-
独立回调microTask:如Promise,其成功/失败回调函数相互独立;
-
复合回调microTask:如 Object.observe, MutationObserver 和NodeJs中的 process.nextTick ,不同状态回调在同一函数体;
MacroTask和MicroTask的执行流程是一个交替执行的过程,代码块执行的时候,按顺序放入异步任务,如果检测到MicroTask,那么将执行它,第一个异步任务结束后会检查是否有同类任务,如果有就继续执行,没有则检查另一类任务是否存在,直至异步任务全部执行完毕。
举个实际开发中遇到的例子
var p = new Promise(function(resolve, reject){
resolve(1);
})
setTimeout(function(){
console.log("setTimeout1");
},0)
p.then(function(value){
console.log("p1");
})
p.then(function(value){
setTimeout(function(){
console.log("setTimeout2");
},0)
console.log("p2");
})
setTimeout(function(){
console.log("setTimeout3");
},0)
p.then(function(value){
console.log("p3");
})
执行结果为p1 p2 p3 setTimeout1 setTimeout3 setTimeout2
promise的优先级是大于setTimeout的,因此3个promise先执行完毕,接着执行setTimeout的时候发现setTimeout2顺序发生了一点变化,真实的setTimeout入栈顺序是setTimeout1->setTimeout3->setTimeout2,因为promise执行完之后setTimeout2才被加入宏异步任务,所以排在最后。