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

2022-05-13 11:42:29 +08:00
 hiw2016

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

  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 页的说法:

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

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

3108 次点击
所在节点    JavaScript
21 条回复
hiw2016
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
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
2022-05-13 12:10:28 +08:00
理解这个对实际开发毫无意义,甚至会起到相反的效果。
Promise 这么优秀的模型,你应该把注意力放在如何组织整个蓝图 (blueprint) 上,而不是这些东西。
shakukansp
2022-05-13 12:23:44 +08:00
你这返回了一个 promise.resolve(),后面的 555 被往后推了一步啊
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
2022-05-13 12:26:44 +08:00
另外不要在携程中试图依赖确定性的调用顺序, 除非你手动同步, 或者使用链式等待
bojue
2022-05-13 12:35:04 +08:00
@lmshl 现在都在这个回字的不同写法的卷,不是说不好但是感觉不应该
shakukansp
2022-05-13 12:43:08 +08:00
在 then 里面 return 233 和 return Promise.resolve(233)
promise 的规范是保证你在以下各 then(val)里拿到的 val 是 233
没说 return 233 和 return Promise.resolve(233) 是一样的
shakukansp
2022-05-13 12:43:59 +08:00
@shakukansp typo 了 保证你在下一个 then(val) 里拿到的 val 是 233
fulvaz
2022-05-13 12:59:33 +08:00
@lmshl 老哥能说说这图是哪来的吗, 有点好奇想继续深入研究下
lmshl
2022-05-13 13:13:55 +08:00
@fulvaz 图是 Scala 的 Cats Effect 纤程库作者的 PPT
<amp-youtube data-videoid="g_jP47HFpWA" layout="responsive" width="480" height="270"></amp-youtube>&t=426s

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

所以这张图也是通用的,面向 blueprint 的设计方法也是通用的。
TWorldIsNButThis
2022-05-13 13:44:07 +08:00
我怎么觉得从语义上看 5 和 34 的顺序关系是无法保证的
除非是 return 第六行的 promise
shakukansp
2022-05-13 14:03:31 +08:00
@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
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
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
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
2022-05-13 14:38:02 +08:00
面试的要是问你就把这个给他,看看他能不能答出来.
hiw2016
2022-05-13 17:16:27 +08:00
感谢各位!@shakukansp 的代码很有直接帮助!以及其他老师们的回答也很有启发~
yugu9138
2022-05-13 22:05:24 +08:00
说实话,你这个代码语法写得是个灾难,
return Promise.resolve() 即可 不需要全部 null ,尽量写到一个流程内
而且这种多 promise 的,可以参考使用 async 更易明白流程结构
yugu9138
2022-05-13 22:11:39 +08:00
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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/852602

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX