为什么 Promise 会有这种表现?

351 天前
 icoming

看下面代码,为什么使用“错误的处理”那种情况。明明所有hasStock很快执行完毕并且返回了结果,但是会在判断没货之间,每个判断之间会卡几秒,不是已经有结果了吗。。。

GPT4 说“错误的处理”应该是秒出结果的,它也没搞明白

// 需要订购的产品链接
const productList = [
  "https://xxx",
  "https://xxx",
  "https://xxx"
]

// 是否有货
const hasStock = async (url: string): Promise<boolean> => {
  const resp = await mAxios.get(url)
  return resp.data.trim() !== ""
}

// 开始订购
const startOrder = async () => {
  const promises = productList.map(u => ({tag: u, promise: hasStock(u)}))

  // 根据是否有货判断购买
  // 正确的处理
  const results = await Promise.allSettled(promises.map(p => p.promise))
  // 错误的处理
  // const results = await Promise.allSettled(promises)
  
  for (const [i, result] of results.entries()) {
    // ...

    // 是否有货,有就订购
    if (!result.value) {
      console.log("无货", promises[i].tag)
      continue
    }
}
2970 次点击
所在节点    JavaScript
20 条回复
Plumbiu
351 天前
这个 promises 并不是 Promise 包裹的对象,秒出的结果的状态也是只是 pending
NessajCN
351 天前
Promise.allSettled(p) 函数的参数 p 类型是 Promise[], 而你代码里的 promises 并不是 Promise[], 而是个 Object[],
而该 Object 的 prototype.promise 才是 Promise , 所以要先 map 成 p.promise 才能传给 Promise.allSettled()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

> The Promise.allSettled() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise.
lisxour
351 天前
@NessajCN op 问的都不是这个问题,人家都写明是错误的处理了
NessajCN
351 天前
@lisxour 传错参数了有啥好纠结为啥这结果的吗?
放其他语言里编译都跑不过的,也就 js 可以这么瞎搞。
你给入参是个数字的函数传个字符串然后来问算出来为啥是这结果,有啥意义?
lisxour
351 天前
首先 gpt 的回复是对的,确实应该是秒出结果的,而且都是 fulfilled

至于为什么是 fulfilled ,是因为你传给 Promise 执行的是非 Promise ,是一个{ tag, promise }对象,而 Promise 执行非 Promise 时,永远都是 fulfilled ,并且返回值就是传进去的值

至于为什么卡顿,问题并不在循环判断有没有货里面,而是 hasStock 这个方法,因为你进行了 await ,所以下面这行代码其实会卡住的

```
const promises = productList.map(u => ({tag: u, promise: hasStock(u)}))
```

你把 hasStock 方法改成这样,你就会发现秒出结果了

```
const hasStock = async (url: string): Promise<boolean> => {
return mAxios.get(url).then(resp => {
return resp.data.trim() !== ""
}, error => false);
}

```
Plumbiu
351 天前
@lisxour 这个 hasStock 和楼主的有什么区别吗
shuax
351 天前
问题出在 promises 数组中的每个元素应该包含 promise 属性的 Promise 对象,而不是一个对象。
henix
351 天前
@NessajCN 传给 allSettled 时 promises 不是已经 map 过了吗
NessajCN
351 天前
@henix 楼主问的是 错误的处理 那行
lisxour
351 天前
@Plumbiu #6 他的进行了 await
Vegetable
351 天前
很遗憾,替换网络请求为 sleep 之后无法复现问题
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

// 是否有货
const hasStock = async (url: string): Promise<boolean> => {
await sleep(1000)
return true
}
Vegetable
351 天前
哦靠我用的是你美注释的代码
Plumbiu
351 天前
@lisxour 不管用不用 await ,放入 Promise.all 里面都一样了吧
lisxour
351 天前
@Plumbiu #13 这行代码会有问题,const promises = productList.map(u => ({tag: u, promise: hasStock(u)}))
tangmou
351 天前
@lisxour #5 即使 hasStock 里面进行了 `await` 也不影响, 因为对于 `hasStock` 本身没有进行 `await`; 这里 `startOrder` 这个函数不会卡顿 (使用 "错误的处理"), 可能的一个原因是, 通过 node.js 处理时, 它会把所有 pending 的 promise 都处理完, 所以是卡在其他地方了. 比如下面这个代码:

```javascript
```
tangmou
351 天前
@lisxour #5
@tangmou 代码忘了贴了:

```javascriipt
// 需要订购的产品链接
const productList = [
"https://xxx",
"https://xxx",
"https://xxx"
]

async function asyncSleep(duration) {
await new Promise(r => setTimeout(r, duration));
}

// 是否有货
async function hasStock(url) {
await asyncSleep(2000);
return false
}

// 开始订购
const startOrder = async () => {
const promises = productList.map(u => ({tag: u, promise: hasStock(u)}))
const results = await Promise.allSettled(promises)
for (const [i, result] of results.entries()) {
// 是否有货,有就订购
if (!result.value) {
console.log("无货", promises[i].tag)
continue
}
}
}

function exitHandler(options, exitCode) {
console.log("process exit:", performance.now());
}

process.on('exit', exitHandler.bind(null,{cleanup:true}));

console.log("before:", performance.now());
startOrder();
console.log("after:", performance.now());
```
tangmou
351 天前
@tangmou #16 为啥我的 markdown 没有渲染出来...
Plumbiu
351 天前
@lisxour 这个也不会阻塞的,只有加上 await 才会阻塞
yin1999
351 天前
这个方法本身允许在可迭代对象中放置非 promise 元素,所以楼上说传参错有问题的其实不对。MDN 文档里面有说:如果传入的 iterable 是非空的,但不包含待定的 promise ,则返回的 promise 仍然是异步兑现的,而不是同步兑现。

所以错误的用法的兑现仍然受到整个运行时的事件循环的影响(调用 allSettled() 方法时会把 CPU 时间片让给其他的异步操作),如果其他操作很占用 CPU 或网络 IO ,那 Promise.allSettled() 本身就不能立刻兑现结果了。你考虑把上面的网络 IO 换成 setTimeout 这种,并减小列表长度,应该就可以“立刻”兑现了。

文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#%E8%BF%94%E5%9B%9E%E5%80%BC
as80393313
350 天前

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

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

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

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

© 2021 V2EX