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

请教一个有关 Promise 执行顺序的问题,有关 resolve 一个 promise 以及注册 callback 的顺序

  •  
  •   benyuwan · 2018-03-03 23:57:24 +08:00 · 2488 次点击
    这是一个创建于 2450 天前的主题,其中的信息可能已经有所发展或是发生改变。
    const original = Promise.resolve(2)
    new Promise(resolve => {
    	resolve(original)
    	Promise.resolve().then(() => Promise.resolve().then(() => console.log(1)))
    	console.log(4)
    }).then(t => console.log(t))
    console.log(3)
    

    这样一段代码.

    按照我的理解

    因为 original 已经被 resolve,所以它的状态被 new Promise 继承

    然后执行 Promise.resolve 并注册回调

    然后打印 4

    然后注册 new Promise 的回调

    然后打印 3

    然后执行第一个注册的回调,并注册第三个回调

    然后打印 t,也就是 2

    然后执行第三个回调,也就是 1

    可是执行结果却是 4,3,1,2

    想不通为什么,特来求教!

    谢谢各位大牛~~~

    7 条回复    2018-03-04 13:59:17 +08:00
    noe132
        1
    noe132  
       2018-03-04 07:35:04 +08:00
    根据 Promise A+ 规范 https://promisesaplus.com/#point-34

    Promise 的执行顺序是和平台相关的。promise 链的执行是在当前 event loop 的主线程结束后的某一时刻进行,具体是何时得看 js 引擎的实现或者 polyfill 的实现。( 2.2.4 )

    从 v8 的实现来看,Promise 的回调是存放在 microtasks 里,event loop 会先完成 microtasks 再去执行 tasks
    从 polyfill 来看,得看实现的方式。如果用 mutationObserver,会和 v8 的顺序相似。如果使用 setTimeout 或者 setImmediate,顺序就会有所不同了。总之,不同 Promise 的执行顺序并不在 Promise 的规范里,顺序也是没法保障的,只不过可以对特定情况进行分析,来了解当前 JS 解释器的执行顺序。例如我用 bulebird 的结果是 4, 3, 2, 1。

    我尝试分析了一下,
    :3 resolve(original)这一步在
    :6 .then(t => console.log(t))
    时,由于 original 是 Promise,在执行 promise resolution procedure(参考规范第 2.3 节)的时候比 resolve 非 promise 的值多了一步,需要等待被 resolve 的 promise fullfilled 或 rejected。我猜想由于是懒惰执行,所以到第 6 行 then 的时候在发现 resolve 的是一个 promise,需要等待这个 promise fullfill,于是就把:6 的 then 的回调 chain 在了 original 的后面(参考 2.3.2 )

    我把格式略微优化了一下,附上 microtasks queue 的操作过程,如下图


    总结来看,这种顺序是不能作为逻辑依赖的,因为规范并没有这样要求,能保证的只是每一个 chain 的顺序。
    我对 v8 了解不多,所以分析过程可能不严谨,也许会有些错误,仅供参考,详细了解还得参考 promise A+和 v8 的实现。
    noe132
        2
    noe132  
       2018-03-04 07:37:29 +08:00
    如果把 original 从 Promise 替换成一个非 promise 的值如 2
    结果就是 4, 3, 2, 1 了,大概就是在 pop 11 的时候直接执行了,而不是再 chain 一步等待 original 去 fullfill
    des
        3
    des  
       2018-03-04 07:54:03 +08:00 via Android
    后排说一下,移动端是 4321
    benyuwan
        4
    benyuwan  
    OP
       2018-03-04 10:30:10 +08:00
    @noe132 感谢你的回答!不过我有一些疑惑的地方。。

    首先我确实只在 chrome safari 和 ff 上试过。

    这三个浏览器的实现是一致的。。

    其次 Promise.resolve(1)应该是会立刻 fulfilled 吧?

    然后 resolve(original)继承了它的状态。

    关于你的解释我无法理解的一点是,一个没有 fulfilled 的 promise,它的 callback 为什么会加入到 micro queue 里?
    mdluo
        5
    mdluo  
       2018-03-04 10:43:59 +08:00
    “因为 original 已经被 resolve,所以它的状态被 new Promise 继承” 这句话并不正确,很简单的验证:



    所以可以看到在 Promise 的构造函数的参数即 executor 函数,在执行的时候去 resolve 另外一个 Promise,即使这个 Promise 的状态是 "resolved",也不会在构造函数返回的时候就立即把 promise 对象的状态置为 "resolved",确实是 "pending"。

    但是如果 resolve(original) 和 original.then((value) => resolve(value)) 是等同的话,结果应该是 4 3 2 1 才对)。因为这个 resolve(value) 虽然不是在第一个 event loop 里同步执行的,但是是最早加入 microtask queue 的。说明 V8 在针对 executor 的 resolve 函数的调用时机的处理并不是同步的,其他有些 Promise 的实现(比如 bluebird )是同步的,结果也确实是 4 3 2 1。

    V8 的处理好像可以等同于下图,看了下这样的话不管是结果还是执行过程中的 Promise 状态都是一致的



    总的来说就是,executor 的 resolve 很有可能在 V8 的 Promise 实现里被特殊处理了,resolve(original) 的执行过程都不是在同步代码里,而是加入了 microtask queue。在 microtask queue 里执行的时候又因为是去 resolve 另一个 Promise,相当于 resolve 这个 Promise 的 then 结果,所以又被加入了 microtask queue 的最后面。而最终轮到 resolve(value) 执行的时候,前面已经被一个 Promise.resolve().then() 的回调、以及这个回调带来的另外一个回调给 “插队” 了,所以 resolve(value) 的执行被排在了最后。

    没具体去看 V8 的代码,仅仅从表现上分析的。不过这一点确实是规范里也没有提到的东西,跟实现有关。
    noe132
        6
    noe132  
       2018-03-04 12:43:38 +08:00
    其实问题主要在于 resolve promise 对象的实现。本想看看 v8 是怎么实现的,奈何 c++实在看不懂。。。
    benyuwan
        7
    benyuwan  
    OP
       2018-03-04 13:59:17 +08:00
    @mdluo 你好,感谢你的回答

    不知道我理解你的意思理解的对不对?

    V8 对 executor 里的 resolve 一个 promise 并不是同步处理的,而是在注册 callback 的时候将这个 resolve(Promise.resolve())加入到 microtask queue 里,这样就可以解释了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2781 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 11:51 · PVG 19:51 · LAX 03:51 · JFK 06:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.