【问题标题】:async function returning an array of promises not the actual array of values异步函数返回一个承诺数组而不是实际的值数组
【发布时间】:2020-01-14 13:44:58
【问题描述】:

我有一个函数,它需要一个整数数组,然后它将调用另一个函数,该函数将接受一个整数并进行一些检查并返回一个承诺来解决它。

我知道 getDataById 函数并不依赖于执行异步工作,但我的这只是示例..

这是我的代码:

function getDataById(id) {
    return new Promise((resolve, reject) => {
        if (id > 0) resolve(id * 10);
        if (id <= 0) reject("wrong input");
    })
}

async function callService(idList) {
    return await idList.map((id) => getDataById(id));
}

let teamIDList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -1, -2, -3, -4];


callService(teamIDList).then(res => {
    console.log(res);
});

我希望它会返回一个新数字数组和里面的一些字符串。 但它返回了一系列承诺。

【问题讨论】:

  • 你不是await-ing getDataById
  • 使用Promise.all。此外,几乎不需要return await 的东西。
  • 因为 async 和 return await 做同样的事情来确保返回一个 promise?
  • @TianQin,不完全是。 async 做了几件事,(1) 确保返回的任何内容都是 Promise 包装的,(2) 确保异步抛出错误,(3) 使 await 在函数中可用。没有强制在 AsyncFunction 中使用 awaitawait 仅在其后有语句或较大表达式的一部分时才有用。 return await something() 中的 await 是不必要的,因为 return something() 具有相同的净效应。

标签: javascript promise async-await


【解决方案1】:

await 给它一个数组时并没有做任何特别的事情,它只是将数组包装在一个已解析的 promise 中。

要等待承诺解决,请使用Promise.all

async function callService(idList) {
    return await Promise.all(idList.map((id) => getDataById(id)));
}

交替:

function callService(idList) {
    return Promise.all(idList.map((id) => getDataById(id)));
}

(旁注:不需要那个箭头函数:

return await Promise.all(idList.map(getDataById));

)

请注意,它们都按此顺序执行操作:

  • 为每个id 调用getDataById
  • 等待所有这些承诺都实现,或者其中一个失败
  • 如果它们都已实现,则使用结果数组来解决 callService 的承诺(请记住,async 函数总是返回承诺)

如果您想等待对getDataById 的每次调用完成,然后再对getDataById 进行下一次调用,您可以通过一个简单的循环来完成:

// **IF** you wanted to wait for each `getDataById` call to finish
// before starting the next
async function callService(idList) {
    const result = [];
    for (const id of idList) {
        result.push(await getDataById(id));
    }
    return result;
}

(如果你愿意,你也可以把它硬塞进reduce,但它不会给你带来任何好处。)

但我认为您想要上面的第一个代码块,并行启动所有调用并等待它们完成。


您在评论中说您的老板不希望您使用Promise.all,因为如果任何承诺被拒绝,它就会拒绝。这是真的,确实如此。如果您不希望这样,您可能想要使用 allSettled 代替。这是一个很容易填充的Stage 4 proposal(例如,在 JavaScript 引擎中积极实现)。

async function callService(idList) {
    return await Promise.allSettled(idList.map(getDataById));
}

交替:

function callService(idList) {
    return Promise.allSettled(idList.map(getDataById));
}

结果是一个对象数组,其中已履行的承诺如下所示:

{
    status: "fulfilled",
    value: (the value)
}

被拒绝的看起来像这样:

{
    status: "fulfilled",
    reason: (the rejection reason)
}

显然,如果您想转换该数组,您可以轻松地做到这一点。例如,如果您想为无法检索的内容返回 null

async function callService(idList) {
    return (await Promise.allSettled(idList.map(getDataById)))
           .map(entry => entry.status === "fulfilled" ? entry.value : null);
}

或者如果你想过滤掉它们:

async function callService(idList) {
    return (await Promise.allSettled(idList.map(getDataById)))
           .map(entry => entry.status === "fulfilled" ? entry.value : null)
           .filter(entry => entry !== null);
}

如果你想同时过滤和映射,我们可以稍微滥用flatMap::-)

async function callService(idList) {
    return (await Promise.allSettled(idList.map(getDataById)))
           .flatMap(entry => entry.status === "fulfilled" ? entry.value : []);
}

【讨论】:

  • 实际上,我的老板希望我将 Promise.all 更改为 async await 方式..我们认为如果任何一个 Promise 被拒绝,Promise.all 将拒绝..
  • @TianQin - 是的,没错。你想做什么而不是拒绝?你可能想要allSettled
  • @TianQin 这不是非此即彼的情况:async/awaitPromise.all 不可互换。
  • @TianQin - 如果您在问题中提到了所有这些限制,那将会有很多的帮助。尊重你的老板,重新实现轮子很愚蠢:只需使用allSettled 和后处理(请参阅更新的答案)。但是如果你想自己做,看看allSettled polyfill,做起来并不难。但它不使用async/await,因为async/await 不适合对promise 数组进行操作。这就是为什么我们有这些承诺 pimitives Promise.all, Promise.allSettled, Promise.race, ...
  • @T.J Crowder - 非常感谢您对以上非常详细的解释。我正在使用 Promise all,我想我只需将其更改为 Promise.allSettled 并与我的老板交谈。再次感谢您。
猜你喜欢
  • 2021-01-11
  • 2018-12-22
相关资源
最近更新 更多