likes
comments
collection
share

浏览器与Node.js中的事件环

作者站长头像
站长
· 阅读数 24

浏览器中的事件环,完整事件环执行顺序

  • 从上至下执行所有同步代码
  • 执行过程中遇到的宏任务与微任务添加至相应的队列
  • 同步代码执行完毕后,执行满足条件的微任务回调。
  • 微任务队列执行完毕后执行所有满足需求的宏任务回调
  • 循环事件环操作
  • 每执行一个宏任务之后就会立刻检查微任务队列
setTimeout(() => {
  console.log("s1");
  Promise.resolve().then(() => {
    console.log("p1");
  });
  Promise.resolve().then(() => {
    console.log("p2");
  });
});

setTimeout(() => {
  console.log("s2");
  Promise.resolve().then(() => {
    console.log("p3");
  });
  Promise.resolve().then(() => {
    console.log("p4");
  });
});

// 打印
s1
p1
p2
s2
p3
p4

Node.js下的事件环

  • timers:执行setTimeout与setInterval回调
  • pending callbacks:执行系统操作的回调,例如tcp udp
  • idle,prepare:只在系统内部进行使用
  • poll:执行与IO相关的回调
  • check:执行setImmediate中的回调
  • close callbacks:执行close事件的回调
  • 执行同步代码,将不同的任务添加至相应的队列
  • 所有同步代码执行后回去执行满足条件微任务
  • 所有微任务代码执行后会执行timer队列中满足的宏任务
  • timer中的所有宏任务执行完成后就会依次切换队列
  • 在完成队列切换之前会先清空微任务代码
 ┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
 └───────────────────────────┘
每个框被称为事件循环机制的⼀个阶段。
每个阶段都有⼀个 FIFO 队列来执⾏回调。
虽然每个阶段都是特殊的,但通常情况下,当事件循环进⼊给定的阶段时,
它将执⾏特定于该阶段的任何操作,然后执⾏该阶段队列中的回调,
直到队列⽤尽或最⼤回调数已执⾏。
当该队列已⽤尽或达到回调限制,事件循环将移动到下⼀阶段,等等。

加入微任务
 ┌───────────────────────────┐
 │ 同步代码 │
 └─────────────┬─────────────┘
 ┌───────────────────────────┐
 │ process.nextTick │
 │ promise.then() │
 └─────────────┬─────────────┘
 ┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
 └───────────────────────────┘

案例代码

setTimeout(() => {
  console.log("s1");
});

Promise.resolve().then(() => {
  console.log("p1");
});

console.log("start");

process.nextTick(() => {
  console.log("tick");
});

setImmediate(() => {
  console.log("setimmediate");
});

console.log("end");

打印
start
end
tick
p1
s1
setimmediate

// 1,setTimeout存入timer中
// 2,promise进入微任务队列
// 3,输出start
// 4,nextTick进入微任务队列
// 5,setImmediate进入check队列中
// 6,输出end
// 7,微任务中的tick优先级高于promise,先输出tick,后输出p1
// 8,查看timer,输出s1
// 9,查看poll,poll为空,查看check
// 10,输出check中setimmediate

setTimeout(() => {
  console.log("s1");
  Promise.resolve().then(() => {
    console.log("p1");
  });
  process.nextTick(() => {
    console.log("t1");
  });
});

Promise.resolve().then(() => {
  console.log("p2");
});

console.log("start");

setTimeout(() => {
  console.log("s2");
  Promise.resolve().then(() => {
    console.log("p3");
  });
  process.nextTick(() => {
    console.log("t2");
  });
});

console.log("end");
打印
start
end
p2
s1
t1
p1
s2
t2
p3

nodejs与浏览器事件环差异

  • 任务队列数不同,浏览器只有两个队列,nodejs中有6个事件队列
  • 微任务执行时机不同,二者都会在同步代码执行完之后执行微任务,浏览器平台下每当一个宏任务执行完毕后就清空微任务,nodejs平台在事件队列切换时会去清空微任务。
  • 微任务优先级不同,浏览器事件环中,微任务存放于事件队列,先进先出,Nodejs中的process.nextTick先于promise.then

Nodejs事件环常见问题

setTimeout(() => {
  console.log("timeout");
},0);

setImmediate(() => {
  console.log("immdieate");
});

快速执行时,他们的打印顺序可能会交换,setTimeout与setImmediate执行随机,setTimeout有延时。

const fs = require("fs");
fs.readFile("./m1.js", () => {
  setTimeout(() => {
    console.log("timeout");
  }, 0);

  setImmediate(() => {
    console.log("immdieate");
  });
});

在I/O操作的回调中,他们的打印顺序固定,先打印immdieate再打印timeout。事件环中I/O回调在poll队列中执行之后查看微任务队列后切换到check中执行setImmediate,才会切换到timer中执行setTimeout