88

I'm getting compile time error in this code:

const someFunction = async (myArray) => {
    return myArray.map(myValue => {
        return {
            id: "my_id",
            myValue: await service.getByValue(myValue);
        }
    });
};

Error message is:

await is a reserved word

Why can't I use it like this?

WelcomeTo
  • 19,843
  • 53
  • 170
  • 286

6 Answers6

201

You can't do this as you imagine, because you can't use await if it is not directly inside an async function.

The sensible thing to do here would be to make the function passed to map asynchronous. This means that map would return an array of promises. We can then use Promise.all to get the result when all the promises return. As Promise.all itself returns a promise, the outer function does not need to be async.

const someFunction = (myArray) => {
    const promises = myArray.map(async (myValue) => {
        return {
            id: "my_id",
            myValue: await service.getByValue(myValue)
        }
    });
    return Promise.all(promises);
}
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • 1
    in this case array of promises is not slower that classic for let in array ? – stackdave Mar 03 '18 at 06:38
  • @stackdave Probably, but the difference will be inconsequential next to `service.getByValue`, which may well involve a network call... – lonesomeday Mar 03 '18 at 15:23
  • thanks I've start using it, anyway readibility is better than velocity, because most of ES6 async techniques always will be slower, but who care – stackdave Mar 03 '18 at 15:37
  • @lonesomeday Can this handle errors or throttling/delays between calls? – Kyle Pennell Nov 25 '19 at 20:05
  • 1
    @KylePennell Yes. You'd need to handle errors either with a `try..catch` in the async function or with a `catch` handler before returning from the outer function. A throttle could be introduced before the `return` in the async function. – lonesomeday Nov 30 '19 at 14:41
  • Good note would be to specify you need to put `await` in front of `Promise.all`, but here since is directly returned it's not needed since return does `await` for you. – Joan Albert Jun 23 '21 at 14:59
  • @Joan Albert You certainly do not need to put await in. In fact you can't as the function is not asynchronous. Promise.all simply returns a promise, which can just be returned like any JavaScript object. – lonesomeday Dec 21 '21 at 14:54
  • @lonesomeday yes, you're right, if you want to have all the array promises solved, you would need to add the `async` like `... = async (myArray) => ...` – Joan Albert Dec 21 '21 at 17:14
  • @Joan Albert but then the function will return a Promise, as all asynchronous functions do... – lonesomeday Dec 21 '21 at 23:28
75

If you want to run map with an asynchronous mapping function you can use the following code:

const resultArray = await Promise.all(inputArray.map(async (i) => someAsyncFunction(i)));

How it works:

  • inputArray.map(async ...) returns an array of promises - one for each value in inputArray.
  • Putting Promise.all() around the array of promises converts it into a single promise.
  • The single promise from Promise.all() returns an array of values - the individual promises each resolve to one value.
  • We put await in front of Promise.all() so that we wait for the combined promise to resolve and store the array of resolved sub-promises into the variable resultArray.

In the end we get one output value in resultArray for each item in inputArray, mapped through the function someAsyncFunction. We have to wait for all async functions to resolve before the result is available.

MattCochrane
  • 2,900
  • 2
  • 25
  • 35
16

That's because the function in map isn't async, so you can't have await in it's return statement. It compiles with this modification:

const someFunction = async (myArray) => {
    return myArray.map(async (myValue) => { // <-- note the `async` on this line
        return {
            id: "my_id",
            myValue: await service.getByValue(myValue)
        }
    });
};

Try it out in Babel REPL

So… it's not possible to give recommendation without seeing the rest of your app, but depending on what are you trying to do, either make the inner function asynchronous or try to come up with some different architecture for this block.

Update: we might get top-level await one day: https://github.com/MylesBorins/proposal-top-level-await

helb
  • 3,154
  • 15
  • 21
  • thanks, upvoted, but your code return array with empty objects (i.e. `[{}, {}]`). I think I need to include somewhere `await`, but couldn't realize where – WelcomeTo Feb 27 '17 at 16:13
  • What does the `service.getByValue` function look like? – helb Feb 27 '17 at 16:19
  • 2
    it just returns ES6 Promise – WelcomeTo Feb 27 '17 at 18:03
  • 4
    It looks to me like the OP expects an array of id'ed objects as the final result, so in keeping with that I think you probably want `return await Promise.all(myArray.map`... for equivalency. – jib Feb 27 '17 at 21:33
9

it will be 2 instructions, but just shift "await" with the extra instruction

let results = array.map((e) => fetch('....'))
results  = await Promise.all(results)
Abdennour TOUMI
  • 87,526
  • 38
  • 249
  • 254
1

I tried all these answers but no one works for my case because all answers return a promise object not the result of the promise like this:

{
  [[Prototype]]: Promise
  [[PromiseState]]: "fulfilled"
  [[PromiseResult]]: Array(3)
  0: ...an object data here...
  1: ...an object data here...
  2: ...an object data here...
  length: 3
  [[Prototype]]: Array(0)
}

Then I found this answer https://stackoverflow.com/a/64978715/8339172 that states if map function is not async or promise aware. So instead of using await inside map function, I use for loop and await the individual item because he said that for loop is async aware and will pause the loop.

Jabal Logian
  • 1,666
  • 4
  • 24
  • 46
0

When you want each remapped value resolved before moving on to the next, you can process the array as an asynchronous iterable.

Below, we use library iter-ops, to remap each value into promise, and then produce an object with resolved value, because map itself shouldn't be handling any promises internally.

import {pipe, map, wait, toAsync} from 'iter-ops';

const i = pipe(
    toAsync(myArray), // make asynchronous
    map(myValue => {
        return service.getByValue(myValue).then(a => ({id: 'my_id', myValue: a}))
    }),
    wait() // wait for each promise
);

(async function() {
    for await (const a of i) {
        console.log(a); // print resulting objects
    }
})

After each value, we use wait to resolve each remapped value as it is generated, to keep resolution requirement consistent with the original question.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138