V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
hiw2016
V2EX  ›  JavaScript

在 then 中末尾返回 Promise.resolve(),为什么改变了进入微任务队列的顺序呢?

  •  
  •   hiw2016 · 2022-05-13 11:42:29 +08:00 · 3154 次点击
    这是一个创建于 955 天前的主题,其中的信息可能已经有所发展或是发生改变。

    code

    对于上述代码,我的理解:

    1. 第 4 行.then方法所属对象已经resolved,所以 5 ~ 20 行代码进入微任务队列
    2. 跳转第 21 行,.then方法所属对象pending中,所以将 23 ~ 25 行加入第 5 行函数返回值对象的PromiseFulfill属性中
    3. 同步代码结束,从微任务队列中取出 5 ~ 20 行代码执行
    4. 6 ~ 8 行创建一个 resolved Promise 对象,所以 10 ~ 12 行代码进入微任务队列,14 行的then会将 15 ~ 17 行的代码加入第 9 行函数返回值对象的PromiseFulfill属性中
    5. 5 ~ 20 行代码执行结束,所以第 5 行返回的期约对象落定为resolved,因此将 23 ~ 25 行加入微任务队列
    6. 目前微任务队列中有两个任务,10 ~ 12 、23 ~ 25 ,在执行 10 ~ 12 后(打印 333 ),15 ~ 17 被加入微任务队列,然后执行 23 ~ 25 (打印 555 ),然后执行 15 ~ 17 (打印 444 )

    实际输出和我上述的理解是一致的( 333 、555 、444 )。

    按照 JavaScript 高级程序设计(第 4 版) 330 页的说法:

    OraO41.jpg

    如果没有显式的返回语句,则 Promise.resolve()会包装默认的返回值 undefined 。

    那实际上面例子中 5 ~ 20 行代码,是没有显式的返回语句的,按照我的理解,就相当于执行了 19 行的return Promise.resolve(),但是当我取消注释 19 行后,输出结果变成了 333 、444 、555 。结果和我已经形成的理解不一致,想不太明白,还请各位老师帮助答疑解惑,万分感谢。

    21 条回复    2022-05-15 17:23:22 +08:00
    hiw2016
        1
    hiw2016  
    OP
       2022-05-13 11:43:06 +08:00
    文字版代码如下:

    ```js
    new Promise(resolve => {
    resolve();
    })
    .then(
    () => {
    new Promise((resolve) => {
    resolve();
    })
    .then(
    () => {
    console.log(333);
    }
    )
    .then(
    () => {
    console.log(444);
    }
    );
    // return Promise.resolve();
    }
    )
    .then(
    () => {
    console.log(555);
    }
    );
    ```
    hiw2016
        2
    hiw2016  
    OP
       2022-05-13 11:43:41 +08:00
    new Promise(resolve => {
    resolve();
    })
    .then(
    () => {
    new Promise((resolve) => {
    resolve();
    })
    .then(
    () => {
    console.log(333);
    }
    )
    .then(
    () => {
    console.log(444);
    }
    );
    // return Promise.resolve();
    }
    )
    .then(
    () => {
    console.log(555);
    }
    );
    lmshl
        3
    lmshl  
       2022-05-13 12:10:28 +08:00   ❤️ 2
    理解这个对实际开发毫无意义,甚至会起到相反的效果。
    Promise 这么优秀的模型,你应该把注意力放在如何组织整个蓝图 (blueprint) 上,而不是这些东西。
    shakukansp
        4
    shakukansp  
       2022-05-13 12:23:44 +08:00
    你这返回了一个 promise.resolve(),后面的 555 被往后推了一步啊
    thinkershare
        5
    thinkershare  
       2022-05-13 12:24:14 +08:00
    因为规范并不保证 333, 444, 555 的执行顺序, 它唯一保证的就是 444 总应该在 333 后面, 而 333, 444, 555 的确定性顺序是未定义行为, 我猜想是编译器优化了无返回值的情况, 这样 555 就更快的得到了执行(还没有执行到 444, 当你手动编写了 Promise.resolve()后, 这个执行需要消耗时间, 这个期间, 444 的任务链条可能已经结束了执行, 因此就是你看到的 333, 444, 555, 不过正如楼上所说, 这些对实际开发影响很小. 你如果除了对 what, 还对 why 感兴趣, 也可以自己深入去研究一下
    thinkershare
        6
    thinkershare  
       2022-05-13 12:26:44 +08:00
    另外不要在携程中试图依赖确定性的调用顺序, 除非你手动同步, 或者使用链式等待
    bojue
        7
    bojue  
       2022-05-13 12:35:04 +08:00
    @lmshl 现在都在这个回字的不同写法的卷,不是说不好但是感觉不应该
    shakukansp
        8
    shakukansp  
       2022-05-13 12:43:08 +08:00   ❤️ 1
    在 then 里面 return 233 和 return Promise.resolve(233)
    promise 的规范是保证你在以下各 then(val)里拿到的 val 是 233
    没说 return 233 和 return Promise.resolve(233) 是一样的
    shakukansp
        9
    shakukansp  
       2022-05-13 12:43:59 +08:00
    @shakukansp typo 了 保证你在下一个 then(val) 里拿到的 val 是 233
    fulvaz
        10
    fulvaz  
       2022-05-13 12:59:33 +08:00
    @lmshl 老哥能说说这图是哪来的吗, 有点好奇想继续深入研究下
    lmshl
        11
    lmshl  
       2022-05-13 13:13:55 +08:00   ❤️ 1
    @fulvaz 图是 Scala 的 Cats Effect 纤程库作者的 PPT
    &t=426s

    但 stackless coroutine 的本质概念都是一样的,而 stackful coroutine 和 stackless coroutine 又是理论上等价,可以转换的,很多语言都能同时支持这两种,比如 JS 的 Promise 和 async / await 。

    所以这张图也是通用的,面向 blueprint 的设计方法也是通用的。
    TWorldIsNButThis
        12
    TWorldIsNButThis  
       2022-05-13 13:44:07 +08:00 via iPhone
    我怎么觉得从语义上看 5 和 34 的顺序关系是无法保证的
    除非是 return 第六行的 promise
    shakukansp
        13
    shakukansp  
       2022-05-13 14:03:31 +08:00   ❤️ 1
    @TWorldIsNButThis
    可以保证,如果 19 行注释掉那么假设 4 行的顺序为 1 ,6 行的 new Promise 也是 1 ,9 行和 22 行的 then 是 2 ,14 行 then 是 3
    如果 19 行不注释,那么假设 4 顺序为 1 ,6 行 new Promise 是 1 ,依照规范,如果.then 中 return 的 x 是 promise 对象,那么当前 then 的状态变为此 promise 状态,所以当前的 then 必须等待 19 行的 Promise.resolve()
    所以顺序变为 9 和 19 行是顺序 2 ,14 和 22 行为顺序 3
    shakukansp
        14
    shakukansp  
       2022-05-13 14:26:49 +08:00
    new Promise(resolve => {
    resolve();
    })
    .then(
    () => {
    new Promise((resolve) => {
    resolve();
    })
    .then(
    () => {
    console.log(333);
    }
    )
    .then(
    () => {
    console.log(444);
    }
    );
    return Promise.resolve().then((val) => {
    console.log(233);
    }).then(() => {
    console.log(888);
    }).then(()=>{
    console.log(999);
    })
    }
    )
    .then(
    () => {
    console.log(555);
    }
    );

    顺序:
    333 和 233 平行,按照声明顺序
    444 和 888 平行,按照声明顺序
    接着 999
    而 555 要等到上一个 then 中 return 的 promise 的最后一个 then resolve
    所以 555 最后被输出

    输出
    333
    233
    444
    888
    999
    555

    楼主你自己再好好想想吧
    rabbbit
        15
    rabbbit  
       2022-05-13 14:32:44 +08:00
    别抠这个了,规范没定指不定哪天浏览器实现就变了.
    来猜猜啥时候会输出 0-0.

    new Promise((r) => {
      console.log('in p0');
      r(new Promise((r) => {
       console.log('in internal p');
       r();
     }));
    })
    .then(() => { console.log('0-0') })

    new Promise((r) => {
      console.log('in p1');
      r();
    })
    .then(() => { console.log('1-0') })
    .then(() => { console.log('1-1') })
    .then(() => { console.log('1-2') })
    .then(() => { console.log('1-3') });
    rabbbit
        16
    rabbbit  
       2022-05-13 14:36:44 +08:00
    再来猜猜这个,啥时候输出 0-0

    let thenable = {
      then: function(resolve, reject) {
       console.log('in thenable');
       resolve(42);
     }
    };

    new Promise((r) => {
      console.log('in p0');
      r(thenable);
    })
    .then(() => { console.log('0-0') })

    new Promise((r) => {
      console.log('in p1');
      r();
    })
    .then(() => { console.log('1-0') })
    .then(() => { console.log('1-1') })
    .then(() => { console.log('1-2') })
    .then(() => { console.log('1-3') });
    rabbbit
        17
    rabbbit  
       2022-05-13 14:38:02 +08:00
    面试的要是问你就把这个给他,看看他能不能答出来.
    hiw2016
        18
    hiw2016  
    OP
       2022-05-13 17:16:27 +08:00
    感谢各位!@shakukansp 的代码很有直接帮助!以及其他老师们的回答也很有启发~
    yugu9138
        19
    yugu9138  
       2022-05-13 22:05:24 +08:00 via iPhone
    说实话,你这个代码语法写得是个灾难,
    return Promise.resolve() 即可 不需要全部 null ,尽量写到一个流程内
    而且这种多 promise 的,可以参考使用 async 更易明白流程结构
    yugu9138
        20
    yugu9138  
       2022-05-13 22:11:39 +08:00 via iPhone
    Promise.resolve().then(_=>{
    return Promise.resolve("1111")
    }).then(c=>{
    console.log(c) //11111
    return Promise.resolve("2222")
    }).then(c=>{
    console.log(c) //222
    return Promise.resolve(c)
    })

    Async:

    let _ = await Promise.resolve();
    let c = await Promise.resolve("11111");
    console.log(c); //1111
    let j = await Promise.resolve("22222")
    console.log(j) //2222
    celebrityii
        21
    celebrityii  
       2022-05-15 17:23:22 +08:00
    返回 Promise.resolve() 后,只有当该 Promise 被解决之后,它之后的 then 语句才会被加入微任务队列
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1249 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 17:56 · PVG 01:56 · LAX 09:56 · JFK 12:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.