JS的事件循环

宿主(如浏览器)发起的任务称为宏观任务

JavaScript 引擎发起的任务称为微观任务

macro-task(宏任务) 大概包括:

  • 定时器类:setTimeout、setInterval、setImmediate
  • I/O操作:比如读写文件
  • 消息通道:MessageChannel
  • script(整体代码)

micro-task(微任务) 大概包括:

  • process.nextTick
  • Promise.then
  • async / await (等价于 Promise.then)
  • MutationObserver(HTML5 新特性)

总体结论就是:

  • 执行宏任务
  • 然后执行宏任务产生的微任务
  • 若微任务在执行过程中产生了新的微任务(将新的微任务放于队列中)继续执行剩余微任务
  • 微任务执行完毕,再回到宏任务中进行下一轮循环

 一道常见的事件循环题目:

复制代码
console.log('script start')

async function async1() {
    await async2()
    console.log('async1 end')
}

async function async2() {
    console.log('async2 end')
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise')
    resolve()
}).then(function() {
    console.log('promise1')
}).then(function() {
    console.log('promise2')
})

console.log('script end')
复制代码

输出:// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

值得注意的是:

async / await 执行顺序

我们知道 async 会隐式返回一个 Promise 作为结果的函数,那么可以简单理解为:await 后面的函数在执行完毕后,await 会产生一个微任务(Promise.then 是微任务)。

但是我们要注意微任务产生的时机,它是执行完 await 后,直接跳出 async 函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。

其他代码执行完毕后,再回到 async 函数去执行剩下的代码,然后把 await 后面的代码注册到微任务队列中。

// 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

新版的 chrome 并不是像上面那样的执行顺序,它优化了 await 的执行速度,await 变得更早执行了,输出变更为:

// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

但是这种做法其实违反了规范,但是规范也可以更改的,这是 V8 团队的一个 PR 

我们可以分两种情况进行讨论

  1. 如果 await 后面直接跟的为一个变量,比如 await 1 。这种情况相当于直接把 await 下面行的代码注册为一个微任务,可以简单理解为 Promise.then(await 下面行的代码),然后跳出函数去执行其他的代码。

  2. 如果 await 后面跟的是一个异步函数的调用,比如上面的代码修改为:

复制代码
console.log('script start')

async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
    return Promise.resolve().then(()=>{
        console.log('async2 end1')
    })
}
async1()

setTimeout(function() {
    console.log('setTimeout')
}, 0)

new Promise(resolve => {
    console.log('Promise')
    resolve()
}).then(function() {
    console.log('promise1')
}).then(function() {
    console.log('promise2')
})

console.log('script end')
复制代码

输出:// script start => async2 end => Promise => script end => async2 end1 => promise1 => promise2 => async1 end => setTimeout

此时 执行完 await 并不会把 await 后面的代码注册到微任务对立中,而是执行完 await 之后,直接跳出了函数,执行其他同步代码,直到其他代码执行完毕后,再回到这里将 await 后面的代码推倒微任务队列中执行。

注意,此时微任务队列中是有之前注册的其他微任务,所以这种情况会先执行其他的微任务。可以理解为 await 后面的代码会在本轮循环的最后被执行。

node的事件循环:暂无

posted on   sss大辉  阅读(248)  评论(0编辑  收藏  举报

编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示