EventLoop事件循环新理解

34 阅读4分钟

直接上代码

console.log('1. 同步代码开始');

process.nextTick(() => {
  console.log('2. nextTick 1');
  process.nextTick(() => console.log('3. nextTick 1 内部的 nextTick'));
});

setTimeout(() => {
  console.log('4. setTimeout 0');
  process.nextTick(() => console.log('5. setTimeout 0 内部的 nextTick'));
}, 0);

setImmediate(() => {
  console.log('6. setImmediate');
  process.nextTick(() => {
    console.log('7. setImmediate 内部的 nextTick');
    setImmediate(() => console.log('8. setImmediate 内部的 setImmediate'));
  });
});

const { port1, port2 } = new MessageChannel();
port1.onmessage = () => {
  console.log('9. MessageChannel 消息');
  process.nextTick(() => console.log('10. MessageChannel 内部的 nextTick'));
};
port2.postMessage('触发');

Promise.resolve().then(() => {
  console.log('11. Promise resolve 微任务');
  process.nextTick(() => console.log('12. Promise 内部的 nextTick'));
  setTimeout(() => console.log('13. Promise 内部的 setTimeout'), 0);
});

queueMicrotask(() => {
  console.log('14. queueMicrotask');
  process.nextTick(() => console.log('15. queueMicrotask 内部的 nextTick'));
});

new Promise((resolve) => {
  console.log('16. Promise 构造函数同步代码');
  resolve('17. Promise 解析值');
}).then((value) => {
  console.log(value);
  return new Promise((resolve) => {
    console.log('18. 第二个 then 中的 Promise 构造函数');
    setImmediate(() => {
      console.log('19. 内层 Promise 的 setImmediate');
      resolve('20. 内层 Promise 解析值');
    });
  });
}).then((value) => {
  console.log(value);
  process.nextTick(() => console.log('21. 第三个 then 中的 nextTick'));
});

setTimeout(() => {
  console.log('22. setTimeout 0 (第二个)');
  process.nextTick(() => {
    console.log('23. 第二个 setTimeout 内部的 nextTick');
    Promise.resolve().then(() => console.log('24. nextTick 内部的微任务'));
  });
}, 0);

setImmediate(() => {
  console.log('25. setImmediate (第二个)');
  process.nextTick(() => console.log('26. 第二个 setImmediate 内部的 nextTick'));
});

console.log('27. 同步代码结束');

解析过程

现在的AI很方便得到答案,但是不要过于相信AI给的答案,AI也是很容易出错的

  1. 同步代码执行,输出1
  2. 将nextTick回调,放入nextTick队列
  3. 将setTimeout回调,放入宏任务队列
  4. 将setImmediate回调,放入宏任务队列
  5. 将onmessage回调,放入宏任务队列
  6. 将promise.then的回调放入微任务队列
  7. 将queueMicrotask回调放入微任务队列
  8. 同步代码执行Promise的立即执行函数,输出16
  9. 将.then方法回调放入微任务队列
  10. setTimeout回调放入宏任务队列
  11. setImmediate放入宏任务队列
  12. 同步代码执行,输出27

同步代码执行完毕,现在有三个任务队列,nextTick队列,微任务队列,宏任务队列

  1. 执行nextTick队列里面的任务,一次性全部取出nextTick任务
  2. 执行,输出2
  3. 产生新的nextTick回调,放入nextTick队列
  4. 再取出nextTick队列里面的任务执行
  5. 输出3,nextTick队列清空

nextTick代码执行完毕,现在去处理微任务队列

  1. 一次性全部取出微任务队列的任务,放到执行栈
  2. 输出11, 产生一个nextTick任务,放入nextTick队列,产生一个宏任务,放入宏任务队列
  3. 输出14, 产生一个nextTick任务,放入nextTick队列
  4. 输出17
  5. Promise内部代码立即执行, 输出18,产生一个宏任务,放入宏任务队列
  6. 此时微任务队列已清空,并且没有产生新的微任务

微任务代码执行完毕,现在nextTick任务队列有任务,继续处理nextTick的任务

  1. 输出12
  2. 输出15

nextTick执行完毕,没有微任务,现在开始处理宏任务,宏任务一个一个取出执行的,现在的宏任务有三种,setTimeout,messageChange, setImmediate,并且优先级是从大到小(备注:这里是node执行环境,在node18版本及以后,setTimeout优先级高于messageChange,16版本及以前正好相反)

  1. 我的node版本是22,所以先执行setTimeout
  2. 输出4, 产生一个nextTick任务
  3. 第一个宏任务执行完毕,处理nextTick任务
  4. 输出5
  5. 取出第二个宏任务执行,输出22,产生一个nextTick任务
  6. 取出nextTick任务执行,输出23,产生一个微任务
  7. 取出微任务执行,输出24
  8. 取出第三个宏任务执行,输出13
  9. setTimeout宏任务全部执行完毕,取出messageChannel宏任务执行
  10. 输出9,产生一个nextTick任务
  11. 取出nextTick任务执行,输出10
  12. setTimeout和messageChannel都执行完毕了,执行setImmediate
  13. 输出6, 产生一个nextTick任务
  14. 取出nextTick任务执行,输出7,产生一个宏任务,放入宏任务队列
  15. 取出下一个宏任务执行,输出25,产生一个nextTick任务
  16. 取出nextTick任务执行,输出26
  17. 取出下一个宏任务执行, 输出19, 产生一个微任务,放入微任务队列
  18. 取出微任务执行,输出20, 产生一个nextTick任务,放入nextTick队列
  19. 取出nextTick任务执行,输出21
  20. 取出最后一个宏任务执行, 输出8

总结

  1. setTimeout,messageChannel,setImmediate是有优先级顺序的,在node16版本以前和16版本以后,优先级不一样
  2. 在node中nextTick的优先级比微任务更高,并且它和微任务一样,是一次性全部取出执行,执行过程中,又产生了nextTick,继续取出执行
  3. 微任务的执行,是一次性全部取出执行,执行过程中如果又产生了微任务,继续执行
  4. 宏任务是一个一个取出执行