Tian Qin
Tian Qin

Reputation: 183

async function returning an array of promises not the actual array of values

I have a function which will take am array of intergers, then it will call an other function which will take one interger and do some check and return a promise to resolve it.

I know that getDataById function is not relly doing async job, but keep in my this is just examples..

This is my code:

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);
});

I expect it will return an array of new numbers and some strings inside. But it returned an array of promises.

Upvotes: 0

Views: 3209

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074355

await doesn't do anything special when you give it an array, it just wraps the array in a resolved promise.

To wait for the promises to settle, use Promise.all:

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

Alternately:

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

(Side note: No need for that arrow function:

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

)

Note that both of those do things in this order:

  • Calls getDataById for each id
  • Waits for all of those promises to be fulfilled, or for one of them to fail
  • If they're all fulfilled, uses the array of results to resolve the callService's promise (remember, async functions always return promises)

If you wanted to wait for each call to getDataById to complete before doing the next call to getDataById, you'd do it with a simple loop:

// **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;
}

(You can also shoehorn that into a reduce if you want, but it doesn't buy you anything.)

But I think you want the first code block above, starting all the calls in parallel and waiting for them to finish.


You've said in a comment that your boss doesn't want you to use Promise.all because it rejects if any of the promises rejects. That's true, it does. If you don't want that, you may want to use allSettled instead. That's a Stage 4 proposal (e.g., actively being implemented in JavaScript engines) which is easily polyfilled.

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

Alternately:

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

The result is an array of objects where the fulfilled promises look like this:

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

and the rejected ones look like this:

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

Obviously, if you want to transform that array, you can easily do so. For instance, if you wanted to return null for the ones you couldn't retrieve:

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

Or if you wanted to filter them out:

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

If you want to filter and map simultaneously, we can slightly abuse flatMap: :-)

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

Upvotes: 3

Related Questions