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
想不通为什么,特来求教!
谢谢各位大牛~~~
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 的实现。 |
2
noe132 2018-03-04 07:37:29 +08:00
如果把 original 从 Promise 替换成一个非 promise 的值如 2
结果就是 4, 3, 2, 1 了,大概就是在 pop 11 的时候直接执行了,而不是再 chain 一步等待 original 去 fullfill |
3
des 2018-03-04 07:54:03 +08:00 via Android
后排说一下,移动端是 4321
|
4
benyuwan OP @noe132 感谢你的回答!不过我有一些疑惑的地方。。
首先我确实只在 chrome safari 和 ff 上试过。 这三个浏览器的实现是一致的。。 其次 Promise.resolve(1)应该是会立刻 fulfilled 吧? 然后 resolve(original)继承了它的状态。 关于你的解释我无法理解的一点是,一个没有 fulfilled 的 promise,它的 callback 为什么会加入到 micro queue 里? |
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 的代码,仅仅从表现上分析的。不过这一点确实是规范里也没有提到的东西,跟实现有关。 |
6
noe132 2018-03-04 12:43:38 +08:00
其实问题主要在于 resolve promise 对象的实现。本想看看 v8 是怎么实现的,奈何 c++实在看不懂。。。
|