V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lazyczx
V2EX  ›  问与答

关于 Promise 添加 microTask 时机的问题

  •  1
     
  •   lazyczx · 2 天前 · 285 次点击

    我写了下面这些代码:

    const delay = async (time) => {
      return new Promise(resolve => setTimeout(resolve, time))
    }
    
    const busy = (time) => {
      const now = new Date()
      while(new Date() - now < time){}
    }
    
    const getNum = () => {
    console.log("getnum 执行")
    return new Promise(async (resolve,reject) => {
        console.log("promise 开始")
        resolve(1)
        addMicro("haha")
        console.log("promise 结束")
        addMicro("===")
    });
    };
    const addMicro = (str) => {
      Promise.resolve().then(() => {
        console.log(`micro task ${str}`);
    });
    }
    
    
    try {
      await getNum()
      console.log("1212") // 先执行 1212 还是 micro task ,先执行 1212
    } catch (err) {
      console.error(err)
    }
    

    并且我本来以为 resolve(1) 会将 await 后的代码先添加到 microTaskQueue ,然后再是 addMicro 函数添加了两个 microTasks ,所以队列先进先出应该先打印 1212 再是两个 "micro task xxx" 但是结果却是先打印了 "micro task xx"再打印 1212

    问了 chatGPT 完全的胡言乱语,还是问题 copilot 得到的答案似乎有些说服力:

    Your question is about the execution order of microtasks in JavaScript.
    
    The key to understanding this is to recognize that await is essentially syntactic sugar for Promise.then(). When you use await, the code after the await expression is wrapped in a Promise.then() callback.
    
    In your example, await getNum() is equivalent to getNum().then(() => { console.log("1212") }).
    
    Now, let's analyze the execution order:
    
    getNum() is called, which returns a Promise.
    The Promise is resolved immediately with the value 1.
    The addMicro("haha") and addMicro("===") calls are executed, which add microtasks to the microtask queue.
    The Promise.then() callback associated with await getNum() is added to the microtask queue.
    The microtask queue is executed. Since addMicro("haha") and addMicro("===") were added to the queue earlier, they are executed first, logging "micro task haha" and "micro task ===".
    Finally, the Promise.then() callback associated with await getNum() is executed, logging "1212".
    So, to answer your question, the code after await is not added to the message queue earlier than the microtasks added by addMicro(). Instead, the microtasks are executed in the order they were added to the queue.
    
    Here's a simplified example to illustrate this:
    
    javascript
    CopyInsert
    Promise.resolve().then(() => console.log("A"));
    console.log("B");
    Promise.resolve().then(() => console.log("C"));
    Output:
    
    CopyInsert
    B
    A
    C
    In this example, "B" is logged first, followed by "A" and then "C". This is because the microtasks are executed in the order they were added to the queue.
    
    I hope this explanation helps clarify things! Let me know if you have further questions.
    
    
    
    
    
    
    
    11 Sources
    ZC
    
    You
    5:14 pm, Feb 20, 2025
    The Promise.then() callback associated with await getNum() is added to the microtask queue.
    
    why this action is behind the 3 implied adding micro_task action? Isn't it  triggered by resolve(1)?
    
    Codeium
    5:14 pm, Feb 20, 2025
    You are correct that the resolve(1) call triggers the addition of the Promise.then() callback associated with await getNum() to the microtask queue.
    
    However, there's a subtle detail to consider: when resolve(1) is called, it doesn't immediately add the Promise.then() callback to the microtask queue. Instead, it marks the Promise as resolved and schedules the execution of the Promise.then() callback.
    
    In JavaScript, when a Promise is resolved or rejected, the associated Promise.then() callbacks are not executed immediately. Instead, they are added to the microtask queue at the end of the current execution context.
    
    In your example, when resolve(1) is called, it marks the Promise as resolved, but the Promise.then() callback associated with await getNum() is not added to the microtask queue immediately. Instead, it's added to the queue at the end of the current execution context, which is after the addMicro("haha") and addMicro("===") calls have already added their microtasks to the queue.
    
    This is why the microtasks added by addMicro("haha") and addMicro("===") are executed before the Promise.then() callback associated with await getNum().
    
    To illustrate this, consider the following example:
    
    javascript
    CopyInsert
    Promise.resolve().then(() => console.log("A"));
    console.log("B");
    resolve(); // assume this resolves a Promise
    Promise.resolve().then(() => console.log("C"));
    Output:
    
    CopyInsert
    B
    C
    A
    In this example, the resolve() call marks the Promise as resolved, but the Promise.then() callback associated with A is not executed immediately. Instead, it's added to the microtask queue at the end of the current execution context, which is after the console.log("C") statement has already been executed.
    
    I hope this explanation helps clarify things! Let me know if you have further questions.
    

    但是它给的例子我也是看不懂,很怕它又是在信口开河所以来问一下,蹲个佬解答一下,是

    • 我的代码错了?
    • 还是我的想法错了?
    • 还是说 copilot 说的是对的,它只是 mark 了 Promise 被 resolved 了,其实要到执行最后才添加 micro_task ,而 Promise.resolve().then(func) 是由于内部函数是空的所以直接就添加了,所以早于 resolve(1) 造成的添加?

    懒得去翻太多资料了,其实翻资料的时候也像个无头苍蝇,所以借助一下社区的力量。。

    zbinlin
        1
    zbinlin  
       2 天前
    你调用 addMicro 为什么不加 await?
    iceprosurface
        2
    iceprosurface  
       2 天前
    你这段代码:
    ```
    const getNum = () => {
    console.log("getnum 执行")
    return new Promise(async (resolve,reject) => {
    console.log("promise 开始")
    resolve(1)
    addMicro("haha")
    console.log("promise 结束")
    addMicro("===")
    });
    };
    ```
    执行到 addMicro 的时候下一个微任务队列是:

    [addMicro("haha").then, addMicro("===").then]

    我记得理论上 Promise.resolve().then 的回调函数会在当前同步代码执行完毕后加入微任务队列的。
    iceprosurface
        3
    iceprosurface  
       2 天前
    至于 console 1212 因为 await 的原因应该是要等到整个微任务队列清空以后才会执行的吧,这种代码我觉得纯 八股文,如果要保证时序,应该从业务考虑
    kunkunzhang
        4
    kunkunzhang  
       2 天前
    await 就是为了防止异步落后于同步的语法的,虽然在异步执行,但是也要执行完再执行后面的同步代码,不然为什么要 wait
    NoManPlay
        5
    NoManPlay  
       2 天前
    Promise 本身是宏任务,要执行完同步任务和微任务后再执行.then/await
    mc2tap
        6
    mc2tap  
       2 天前
    自己照着 Promise A+实现一遍 Promise 就知道了
    lazyczx
        7
    lazyczx  
    OP
       1 天前
    @iceprosurface
    @mc2tap
    @NoManPlay

    > iceprosurface:我记得理论上 Promise.resolve().then 的回调函数会在当前同步代码执行完毕后加入微任务队列的。

    是因为 then 和 resolve() 是处于同一块同步代码,resolve 的时候本来可以添加 microtask 的,但是由于 then 还没有被执行所以没有 task 可以添加。

    > iceprosurface:至于 console 1212 因为 await 的原因应该是要等到整个微任务队列清空以后才会执行的吧,这种代码我觉得纯 八股文,如果要保证时序,应该从业务考虑

    不是因为 await 的原因,await 我觉得其实就是将当前任务的后续代码包裹成一个 callback 然后用 then 调用了(可能说法有错误),我觉得也不算八股文吧,至少让我搞清楚了 Promise 原理,下次用 Promise 会更有底气不出错,我感觉这样至少心理上挺爽的哈哈。

    > NoManPlay: Promise 本身是宏任务,要执行完同步任务和微任务后再执行.then/await

    我认为 new Promise 的 executer 是同步代码,await 是语法糖,将代码包装成 .then 的回调函数,然后当 Promise resolve 的时候会将已经存在的 onFulfilleds 或者 onRejects 添加为 microtask ,全部过程中我不知道哪个是宏任务,不在认知中

    > mc2tap:自己照着 Promise A+实现一遍 Promise 就知道了

    我看了,但是水平有限最终还是去看别人的实现了,但是看了半天也没关联到这个问题的具体答案,我后面又遇到一个问题,[去 StackOverflow 上问了]( https://stackoverflow.com/questions/79455607/when-does-promise-resolve-reject-actually-add-a-micro-task#comment140127364_79455607),然后连带着这个问题也知道答案了,其实就是 resolve 的时候还没有注册回调函数,所以导致无任务可以添加,直到执行了 then 才添加了任务,被回答者的说法点拨了,我惊觉后回来看这个帖子的问题,答案也是相同的。


    我添加了 setTimeout ,延后了 resolve 和 addMicro 的执行之后,添加 microtask 的次序就按照我想的来了:

    ```js
    const delay = async (time) => {
    return new Promise((resolve) => setTimeout(resolve, time));
    };

    const busy = (time) => {
    const now = new Date();
    while (new Date() - now < time) {}
    };

    const getNum = () => {
    console.log("getnum 执行");
    return new Promise(async (resolve, reject) => {
    console.log("promise 开始");
    setTimeout(() => {
    resolve(1);
    addMicro("haha");
    });
    console.log("promise 结束");
    });
    };
    const addMicro = (str) => {
    Promise.resolve().then(() => {
    console.log(`micro task ${str}`);
    });
    };

    getNum().then(() => {
    console.log("1212");
    });
    ···

    这个代码是先输出 1212 再输出 micro task haha 的
    lazyczx
        8
    lazyczx  
    OP
       1 天前
    嗯?什么玩意儿 v2 的回复区不支持 md 吗?上面的代码和链接都有问题喂?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   931 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 20:29 · PVG 04:29 · LAX 12:29 · JFK 15:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.