在 then 中末尾返回 Promise.resolve(),为什么改变了进入微任务队列的顺序呢? - V2EX
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 3796 次点击
    这是一个创建于 1319 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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     Solana     5785 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 02:39 PVG 10:39 LAX 18:39 JFK 21:39
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86