想要搞清楚promise和settimeout到底哪个先运行,就需要搞明白异步机制和事件循环。
异步
- 同步:在调用函数时,如果可以立刻得到预期的结果就是同步;
- 异步:在调用函数时,如果不能立刻得到预期的结果,需要等待或者将来通过一定的操作获得结果的话就是异步。
js是支持异步阻塞的语言,promise就是一个经典的异步实现。函数的调用并不返回一个真实的结果,而是一个“承诺”,在合适的机会下,再等待这个承诺实现。示例:
var p = new Promise(function(resolve, reject){
console.log("a");
resolve();
});
p.then(function(){
console.log("b");
});
console.log("c");
// => a, c, b
事件循环
通常情况下,js引擎等待宿主环境分配宏观任务,在JavaScript引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换称为事件循环,可以理解为宏观队伍队列就是事件循环。而任务队列又可以分为宏观任务和微观任务,由宿主发起的任务为宏观任务,例如script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境);微观任务则是由js引擎发起的任务,例如,Promise、MutaionObserver、process.nextTick(Node.js 环境)。
在宏观任务中,promise会产生异步代码,js必须保证这些异步代码在一个宏观任务中完成,因此,每一个宏观任务中又包含了一个微观任务队列。当前执行栈执行完成时,会立刻处理完所有的微观队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。 像promise永远会在微观队列的尾部添加事件,而类似于setTimeout等宿主API则会添加宏观任务。那么现在我们就可以看下面这个例子。
var p = new Promise(function(resolve, reject){
console.log("a");
resolve();
});
setTimeout(()=> console.log("b"),0);
p.then(function(){
console.log("c");
});
console.log("d");
// => a, d, c, b
所以我们可以总结出异步执行的步骤:
- 首先我们有多少个宏观任务;
- 在每个宏观任务中,分析有多少个微观任务;
- 根据调用次数,决定宏观任务中,微观任务的执行顺序;
- 根据宏观任务的触发规则和调用次序,确定宏观任务的调用次序;
- 确定整个顺序。
async/await
在ES6之后还加入了新的特性——async/await关键字来编写promise异步函数。在声明异步函数时,要在函数之前加上async关键字,然后在函数体中也要搭配await的关键字来等待一个promise。示例如下:
function resolveAfter2Seconds(){
return new Promise(function(resolve, reject){
setTimeout(resolve,2000);
});
}
async function asyncCall(){
console.log("a");
await resolveAfter2Seconds();
console.log("b");
}
asyncCall();
//=> a, b
await总是会等待promise返回异步函数的结果,所以使用者几乎不需要理解异步函数的概念,同时async和await结构还提供了异步函数的嵌套,以及一个async函数体里可以有多个await等待多个promise的结果。
那么最后,我想你应该知道了promise和setTimeout到底是谁先执行了。
Reference: juejin.cn/post/699216… zhuanlan.zhihu.com/p/33058983 zh.javascript.info/event-loop developer.mozilla.org/en-US/docs/… time.geekbang.org/column/arti…