Eson Wong's Blog

生活随想、学习笔记、读书总结、创作记录

0%

前端面试题解析之 Promise 和 setTimeout 执行顺序

微任务和宏任务的执行顺序是面试中经常会被问到的问题,这里就来解析一下。

下面的代码中,打印的顺序是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log("script 1");

setTimeout(() => console.log("timeout 1"));

Promise.resolve().then(() => console.log("promise 1"));

Promise.resolve().then(() =>
setTimeout(() => console.log("timeout in promise"))
);

Promise.resolve().then(() => console.log("promise 2"));

setTimeout(() => console.log("timeout 2"));

console.log("script 2");

答案是:

1
2
3
4
5
6
7
[Log] script 1
[Log] script 2
[Log] promise 1
[Log] promise 2
[Log] timeout 1
[Log] timeout 2
[Log] timeout in promise

解析

JavaScript 是单线程的,它的任务调度是基于事件循环的。事件循环中有两种任务队列:微任务队列和宏任务队列。

timeout 中的回调函数会被放到宏任务队列中,而 promise 中的回调函数会被放到微任务队列中。

上面代码的的执行顺序如下:

stateDiagram-v2

  MTQ: 宏任务队列
  Script: script 宏任务
  note right of Script: 整个 script 作为一个宏任务
  S1: console.log("script 1")
  T1: setTimeout()
  P1: Promise.resolve().then()
  P2: Promise.resolve().then()
  T2: setTimeout()
  S2: console.log("script 2")

  MicrotaskQ:微任务队列
  PL1: () => console.log("promise 1")
  PT: () => setTimeout(() => console.log("timeout in promise"))
  PL2: () => console.log("promise 2")
  PTL: () => console.log("timeout in promise")
  SL1: () => console.log("timeout 1")
  SL2: () => console.log("timeout 2")


  state MTQ {
    [*] --> Script
    PL2 --> SL1
    SL1 --> SL2
    SL2 --> PTL
  }

  state Script {
    [*] --> S1
    S1 --> T1
    T1 --> P1
    note right of T1: 安排 () => console.log("timeout 1") 到宏任务队列
    P1 --> P2
    note right of P1: 安排 () => console.log("promise 1") 到微任务队列
    P2 --> T2
    note right of P2: 安排 () => console.log("promise 2") 到微任务队列
    T2 --> S2
    note right of T2: 安排 () => console.log("timeout 2") 到宏任务队列
  }

  S2 --> MicrotaskQ

  state MicrotaskQ {
    [*] --> PL1
    PL1 --> PT
    PT --> PL2
    note left of PT: 安排 () => console.log("timeout in promise") 到宏任务队列
  }

宏任务有哪些

  • script
  • setTimeout
  • setInterval
  • setImmediate
  • requestIdleCallback

微任务有哪些

  • Promise 的 then 和 catch
  • process.nextTick (Node.js 环境)
  • MutationObserver (浏览器环境)

补充说明

  1. 宏任务 Macrotask 不是标准的术语, HTML Standard 中的定义是:Task。

  2. Task 和 Macrotask 、 UI rendering 的执行顺序

    flowchart LR
    
    subgraph Event Loop
      Task --> Macrotask1
      subgraph Macrotask Queue
        Macrotask1 --> Macrotask2
      end
      Macrotask2 --> A["requestAnimationFrame() 的回调"]
      A --> UR[渲染 UI]
      UR --> Task
    end
请我喝杯咖啡吧!

欢迎关注我的其它发布渠道