javascript中的事件循环

  1. js引擎执行一个js文件时,会创建一个全局执行上下文,代码依次执行下来,当遇到函数执行时,会再创建一个函数执行上下文,此函数将压入执行栈中,**函数中的同步任务会依次执行下来,例如console.log(xxx)**,当函数中又有函数被调用,则再创建新的执行上下文,并把函数压入执行栈中,以此类推。
  2. 事件循环:
console.log('start');   // 1.开始执行同步任务。
Promise.resolve().then(() => {  // 2.发现有微任务队列,执行微任务中的代码,同时生成一个宏任务↓。
    console.log('Promise1')
    setTimeout(() => {  // 5.执行最后一个宏任务。
        console.log('setTimeout2')
    }, 0)
})
setTimeout(() => {  // 3.这个宏任务先进队列,先执行,此时宏任务中生成了一个微任务。
    console.log('setTimeout1')
    /*
    * 当外面这个宏任务完成之后
    * 会检查微任务队列中有没有没完成的任务
    */
    Promise.resolve().then(() => {
        console.log('Promise2')   // 4. 有,则先完成此微任务再执行宏任务队列中的代码。
    })
}, 0)
console.log('end');   // 1.开始执行同步任务,执行完后检查是否有微任务队列。

// start
// end
// Promise1
// setTimeout1
// Promise2
// setTimeout2

setTimeout执行时,并没有并没有将回调函数放入事件循环队列中,而是在等待时间过去之后才将回调放入,所以,如果此时队列中还有其他任务排在前面,setTimeout的回调并不会马上执行,这就它会是产生误差的原因。

在当前的微任务没有执行完成时,是不会执行下一个宏任务的。

// 4. 最后执行下一个宏任务
setTimeout(_ => console.log(4))

new Promise(resolve => {
    resolve()
    /* 
    * 1.new Promise创建后会立即执行(同步任务)
    * script执行也是属于宏任务
    * 相当于script宏任务生成的微任务
    */
    console.log(1)
}).then(_ => {
    /* 
    * 3.执行Promise后生成的微任务
    * 当前的宏任务生成的微任务没有执行完之前
    * 是不会执行下一个宏任务的
    */
    console.log(3)
})

console.log(2)  // 2.然后执行其它同步任务

Nodejs中的事件循环

阶段概述

  • timer定时器):本阶段执行已经被setTimeout()setInterval()的调度回调函数。
  • I/O callbacks待定回调):处理一些上一轮循环中的少数未执行的I/O回调。
  • idle,perpare:仅系统内部使用。
  • poll轮询):检索新的I/O事件;执行与I/O相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况node将在适当的时候在此阻塞。
  • check检测):setImmediate() 回调函数在这里执行。
  • close callbacks关闭的回调函数):一些关闭的回调函数,如:socket.on('close', ...)

Node规定,process.nextTickPromise的回调函,追加在本轮循环,及同步任务一旦执行完成,就开始执行他们。而setTimeoutsetIntervalsetImmediate的回调函数,追加在次轮循环。

例子:

// nodejs
console.log('start')    // 1.首先执行同步代码
setTimeout(() => {
    console.log('timer1')    // 3.执行timer中的代码,并且生成的微任务推入微任务队列
    Promise.resolve().then(function () {
        console.log('promise1')        // 5.timer执行完成之后执行微任务队列中的代码
    })
}, 0)
setTimeout(() => {
    console.log('timer2')    // 4.timer中的代码都会同步执行,并将微任务推入微任务队列
    Promise.resolve().then(function () {
        console.log('promise2')        // 6.执行微任务中的代码
    })
}, 0)
Promise.resolve().then(function () {
    console.log('promise3')        // 2.然后执行script宏任务生成的微任务
})
console.log('end')    // 1.首先执行同步代码
// start
// end
// promise3
// timer1
// timer2
// promise1
// promise2
// browser
console.log('start')    // 1.执行同步任务
setTimeout(() => {
    console.log('timer1')    // 3.再执行宏任务
    Promise.resolve().then(function () {
        console.log('promise1')        // 4.执行宏任务中创建的微任务
    })
}, 0)
setTimeout(() => {
    console.log('timer2')    // 5.执行事件队列中的其它任务
    Promise.resolve().then(function () {
        console.log('promise2')        // 6.执行宏任务中的微任务
    })
}, 0)
Promise.resolve().then(function () {
    console.log('promise3')        // 2.执行script宏任务创建的微任务
})
console.log('end')    // 1.执行同步任务,并将setTimeout放入事件队列

// start
// end
// promise3
// timer1
// promise1
// timer2
// promise2

timer(定时器)

计时器指定可以执行所提供回调阈值,而不是用户希望其执行的确切时间。在指定的一段时间间隔后, 计时器回调将被尽可能早地运行。但是,操作系统调度或其它正在运行的回调可能会延迟它们。

计时器只是指定一个阈值,处理其它任务超过了这个阈值是可能的,其它任务完成后系统只能尽可能早的去运行定时任务。

简单的讲就是,在node.js启动时,创建了一个类似while(true)的循环体,每次执行一次循环体称为一次tick,每个tick的过程就是查看是否有事件等待处理,如果有,则取出事件极其相关的回调函数并执行,然后执行下一次tick。所以,有如下代码:

A();
B();
C();

它的执行逻辑是,先询问事件观察者当前是否有任务需要执行?观察者回答“有”,于是取出A执行,A是否有回调函数?如果有(如果没有则继续询问当前是否有任务需要执行),则取出回调函数并执行(注意:回调函数的执行基本都是异步的,可能不止一个回调),执行完回调后通过某种方式通知调用者,我执行完了,并把执行结果给你,你自己酌情处理吧,主函数不需要不断询问回调函数执行结果,回调函数会以通知的方式告知调用者我执行完了(don’t call me ,i will call you.),而这个过程主线程并不需要等待回调函数执行完成,它会继续向前执行,即再次询问观察者当前是否还有任务需要执行,重复上面的步骤。。。直到观察者回答没有了,线程结束。