V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
icoming
V2EX  ›  JavaScript

为什么 Promise 会有这种表现?

  •  
  •   icoming · 327 天前 · 2952 次点击
    这是一个创建于 327 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看下面代码,为什么使用“错误的处理”那种情况。明明所有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
        }
    }
    
    20 条回复    2024-01-07 14:08:48 +08:00
    Plumbiu
        1
    Plumbiu  
       327 天前
    这个 promises 并不是 Promise 包裹的对象,秒出的结果的状态也是只是 pending
    NessajCN
        2
    NessajCN  
       327 天前   ❤️ 1
    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
        3
    lisxour  
       327 天前
    @NessajCN op 问的都不是这个问题,人家都写明是错误的处理了
    NessajCN
        4
    NessajCN  
       327 天前   ❤️ 1
    @lisxour 传错参数了有啥好纠结为啥这结果的吗?
    放其他语言里编译都跑不过的,也就 js 可以这么瞎搞。
    你给入参是个数字的函数传个字符串然后来问算出来为啥是这结果,有啥意义?
    lisxour
        5
    lisxour  
       327 天前
    首先 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
        6
    Plumbiu  
       327 天前
    @lisxour 这个 hasStock 和楼主的有什么区别吗
    shuax
        7
    shuax  
       327 天前
    问题出在 promises 数组中的每个元素应该包含 promise 属性的 Promise 对象,而不是一个对象。
    henix
        8
    henix  
       327 天前
    @NessajCN 传给 allSettled 时 promises 不是已经 map 过了吗
    NessajCN
        9
    NessajCN  
       327 天前 via Android
    @henix 楼主问的是 错误的处理 那行
    lisxour
        10
    lisxour  
       327 天前
    @Plumbiu #6 他的进行了 await
    Vegetable
        11
    Vegetable  
       327 天前
    很遗憾,替换网络请求为 sleep 之后无法复现问题
    const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

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

    ```javascript
    ```
    tangmou
        16
    tangmou  
       327 天前
    @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
        17
    tangmou  
       327 天前
    @tangmou #16 为啥我的 markdown 没有渲染出来...
    Plumbiu
        18
    Plumbiu  
       327 天前
    @lisxour 这个也不会阻塞的,只有加上 await 才会阻塞
    yin1999
        19
    yin1999  
       327 天前
    这个方法本身允许在可迭代对象中放置非 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
        20
    as80393313  
       326 天前
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1205 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 18:35 · PVG 02:35 · LAX 10:35 · JFK 13:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.