【问题标题】:Macro task vs micro task宏任务与微任务
【发布时间】:2021-06-15 09:31:18
【问题描述】:

为什么事件循环优先于卡通的 setTimeouts 而不是 then 的 setTimeout?我所知道的是,事件循环应该优先考虑诸如promises之类的微任务,然后执行消息队列(卡通的setTimeouts)。

const tom = () => console.log('Tom');
const jerry = () => console.log('Jerry');
const doggy = () => console.log('Doggy');
const cartoon = () => {
    console.log('Cartoon');
    setTimeout(tom, 0);
    setTimeout(doggy, 0);
    new Promise((resolve, reject) => {
        resolve('I am a Promise, right after tom and doggy! Really?');
    }).then(resolve => {
        console.log(resolve);
        setTimeout(() => {
            console.log('inner timeout')
        }, 0)
    });
    new Promise((resolve, reject) =>
        resolve('I am a Promise after Promise!')
    ).then(resolve => console.log(resolve));
    jerry();
}
cartoon();

输出:

"Cartoon"
"Jerry"
"I am a Promise, right after tom and doggy! Really?"
"I am a Promise after Promise!"
"Tom"
"Doggy"
"inner timeout"

【问题讨论】:

  • .then()s 只是添加到微任务队列中。然后你的微任务是添加一个宏任务。但是已经添加了两个宏任务(tomjerry)。这是一个队列,因此您打印'inner timeout' 的任务将在它们的后面

标签: javascript


【解决方案1】:

警告:不要对承诺时间做出假设(除非履行/拒绝处理程序不会被同步调用,这不是假设,而是真正承诺提供的保证)。做这种事情来探索发生了什么是可以的,作为一个学习练习,但不要让代码依赖于任务和微任务之间的交互。 :-)

为什么事件循环优先于cartoonsetTimeouts 而不是thensetTimeout

因为他们提前安排好了。

据我所知,事件循环应该优先考虑诸如promises之类的微任务,然后执行消息队列(卡通的setTimeouts)。

这与您获得的结果一致。在cartoon 的顶层对setTimeout 的两次调用会立即发生。从承诺履行处理程序对setTimeout 的调用直到那些已经运行之后才会发生。当它们被计划设置它们以后执行的顺序时(因为它们都具有相同的超时值)。 (setTimeout 在履行处理程序中并不重要,即使你在创建承诺的地方调用了setTimeout,你仍然会在其他两个之后调用它,所以它会安排在另外两个。)

(旁注:Promise.resolve(x) 等同于 new Promise(resolve => resolve(x)) 并且更容易输入。:-))

在 cmets 中查看更多解释:

const tom = () => console.log('Tom');
const jerry = () => console.log('Jerry');
const doggy = () => console.log('Doggy');
const cartoon = () => {
    console.log('Cartoon');
    // ↓ Schedules the call to `tom`
    setTimeout(tom, 0);

    // ↓ Schedules the call to `doggy`; it was scheduled later with the same
    // ↓ timeout value, so it will happen after the call to `tom`
    setTimeout(doggy, 0);

    // ↓ Creates a new promise which is immediately fulfilled
    new Promise((resolve, reject) => {
        resolve('I am a Promise, right after tom and doggy! Really?');
    })
    // ↓ Attaches a fulfillment handler to that promise
    .then(resolve => {
        // This handler won't be called until the microtasks queued by the
        // task in which the promise was fulfilled are run

        console.log(resolve);

        // ↓ Schedules the call showing 'inner timeout', which will occur
        // ↓ after the calls to `tom` and `doggy` because it was scheduled
        // ↓ later (it would be even if it weren't in a promise fulfillment
        // ↓ handler, just like `doggy` is called after `tom`)
        setTimeout(() => {
            console.log('inner timeout')
        }, 0)
    });

    // ↓ Creates a new promise which is immediately fulfilled
    new Promise((resolve, reject) =>
        resolve('I am a Promise after Promise!')
    )
    // ↓ Attaches a fulfillment handler to that promise
    .then(resolve => {
        // This handler won't be called until the microtasks queued by the
        // task in which the promise was fulfilled are run

        console.log(resolve);
    });

    // ↓ Calls `jerry` right away, before any microtasks or other tasks scheduled by
    // this task could possibly run
    jerry();
}
cartoon();

要查看实际的任务与微任务调度,这里有一个示例(但请参阅上面的警告):

const task = () => console.log("task");
const microtask1 = () => console.log("microtask1");
const fulfillment1 = () => console.log("fulfillment1 (microtask2)");
const microtask3 = () => console.log("microtask3");
const fulfillment2 = () => console.log("fulfillment2 (microtask4)");

setTimeout(task, 0);
queueMicrotask(microtask1);
Promise.resolve("hi")
.then(fulfillment1)
.then(fulfillment2);
queueMicrotask(microtask3);

// Output:
//
// microtask1
// fulfillment1 (microtask2)
// microtask3
// fulfillment2 (microtask4)
// task

当 JavaScript 引擎被赋予该代码时,它:

  1. 运行任务以执行该代码
    1. 在任务队列中“尽快”安排task
      (这里有些人挥手,但以上内容是准确的;setTimeout(fn, 0) 并不总是尽快安排,不过,如果嵌套,可能会添加一个短暂的延迟)
    2. 将微任务排队 (microtask1)
    3. 排队另一个微任务(fulfillment1) (because then` 被调用以履行承诺)
    4. 排队第三个微任务 (microtask3)
  2. 到达该任务的末尾,并处理微任务队列
    1. 运行microtask1
    2. 运行fulfillment1
      • 这样做会履行第一次调用 then 所创建的承诺,这会将 fulfillment2 作为微任务排队
    3. 运行microtask3
    4. 运行 fulfillment2,因为它已按上面的 #2.2 排队
  3. 到达微任务队列的末尾
  4. 运行下一个可用任务
    1. 致电task

即使task 被安排在任何微任务之前,它也会在它们之后运行,即使是在微任务队列处理期间才被安排的那​​个。

但同样,虽然对学习目的有用,但请记住,上面的确切顺序取决于承诺何时实现。在现实世界的代码中,你有一个承诺,因为你有一个可能需要 N 时间的异步操作,所以你不知道它什么时候会完成,所以你不能假设它什么时候完成将运行反应。

【讨论】:

    【解决方案2】:

    当您调度包含setTimeout 调用的微任务时,该微任务将调度另一个宏任务,该宏任务将添加到宏任务队列的末尾。

    由于队列是先进先出的(先进先出),较早安排的任务在使用微任务安排的任务之前处理。

    在您的情况下,使用以下 setTimeout 调用安排的任务

    setTimeout(tom, 0);
    setTimeout(doggy, 0);
    

    提前安排,因此在使用微任务安排的任务之前处理它们。

    我所知道的是,事件循环应该优先考虑微任务,例如 作为 promises,然后执行消息队列(卡通的 setTimeouts)。

    优先级。

    处理一个微任务队列:

    • 在每次回调之后,只要调用堆栈为空
    • 在每个任务之后

    所以在你的脚本结束后,微任务队列将被处理,直到所有的微任务都被处理完。在您的情况下,第一个微任务将在宏任务队列中安排一个宏任务。

    此时,宏任务和微任务如下所示:

    macro-tasks = [
        () => console.log('Tom'), 
        () => console.log('Doggy'), 
        () => console.log('inner timeout')
    ]
    
    micro-tasks = []
    

    到目前为止的控制台输出是:

    Cartoon
    Jerry    
    I am a Promise, right after tom and doggy! Really?   
    I am a Promise after Promise!
    

    处理完微任务队列后,将处理宏任务队列中的下一个宏任务,即

    () => console.log('Tom') 
    

    以及微任务调度的任务

    () => console.log('inner timeout')`
    

    将在最后处理。

    【讨论】:

      猜你喜欢
      • 2019-01-18
      • 2021-06-28
      • 1970-01-01
      • 2022-11-27
      • 1970-01-01
      • 2014-11-12
      • 2018-07-01
      • 1970-01-01
      相关资源
      最近更新 更多