32

Consider this code

const response  = await fetch('<my url>');
const responseJson = await response.json();
responseJson =  _.sortBy(responseJson, "number");

responseJson[0] = await addEnabledProperty(responseJson[0]);

What addEnabledProperty does is to extend the object adding an enabled property, but this is not important. The function itself works well

async function addEnabledProperty (channel){

    const channelId = channel.id;
    const stored_status = await AsyncStorage.getItem(`ChannelIsEnabled:${channelId}`);
    let boolean_status = false;
    if (stored_status == null) {
        boolean_status = true;
    } else {
        boolean_status = (stored_status == 'true');
    }

    return _.extend({}, channel, { enabled: boolean_status });
}

Is there a way to use _.map (or another system), to loop trough entire responseJson array to use addEnabledProperty against each element?

I tried:

responseJson = _.map(responseJson,  function(channel) {
            return addEnabledProperty(channell);
        });

But it's not using async so it freeze the app.

I tried:

responseJson = _.map(responseJson,  function(channel) {
            return await addEnabledProperty(chanell);
        });

But i got a js error (about the row return await addEnabledProperty(chanell);)

await is a reserved word

Then tried

responseJson = _.map(responseJson, async function(channel) {
            return await addEnabledProperty(channell);
        });

But I got an array of Promises... and I don't understand why...

What else!??

EDIT: I understand your complains about I didn't specify that addEnabledProperty() returns a Promise, but, really, I didn't know it. In fact, I wrote "I got an array of Promises... and I don't understand why "

realtebo
  • 23,922
  • 37
  • 112
  • 189
  • 1
    *"But I got an array of Promises... and I don't understand why..."* Because an `async` function returns a promise and `.map` creates an array of the values returned by the callback. `Promise.all` is the way to "resolve" an array of promises. – Felix Kling Nov 02 '17 at 00:05
  • 2
    Per your third example, any `async` function always returns a promise, which is why you get an array of them. But, you can try: `responseJson = await Promise.all(_.map(responseJson, function(channel) { return addEnabledProperty(channel) }))` – CRice Nov 02 '17 at 00:05
  • `await is a reserved word` where did you get this error? – Jaromanda X Nov 02 '17 at 00:14
  • Does `addEnabledProperty` return a `Promise`? – guest271314 Nov 02 '17 at 00:16
  • @guest271314 I hope so, since OP is `await`ing it in his first snippet. EDIT: I should note that awaiting a non-promise is still valid, so perhaps it doesn't?. – CRice Nov 02 '17 at 00:17
  • @CRice OP does not mention at text of Question that `addEnabledProperty()` returns a `Promise` _"What `addEnabledProperty` does is to extend the object adding an `enabled` property, but this is not important. The function itself works well"_ – guest271314 Nov 02 '17 at 00:18
  • @guest271314 Indeed. Although awaiting a function that doesn't return a promise is pointless, it is still valid JS, so you may be right. I'm just hoping that OP would not await something that's not a promise. – CRice Nov 02 '17 at 00:21
  • I understand your complains about I didn't specify that `addEnabledProperty()` returns a `Promise`, but, really, I didn't know it. In fact, I wrote "_I got an array of Promises... and I don't understand why_ " – realtebo Nov 04 '17 at 17:42

5 Answers5

58

To process your response jsons in parallel you may use Promise.all:

const responseJson = await response.json();
responseJson = _.sortBy(responseJson, "number");

let result = await Promise.all(_.map(responseJson, async (json) => 
  await addEnabledProperty(json))
);

Since addEnabledProperty method is async, the following also should work (per @CRice):

let result = await Promise.all(_.map(responseJson, addEnabledProperty));
dhilt
  • 18,707
  • 8
  • 70
  • 85
  • 4
    No need for the `async` anonymous function. `(json) => addEnabledProperty(json)` is functionally identical. Also, afaik, we can just pass that function to the map directly, without the wrapper: `_.map(responseJson, addEnabledProperty)` – CRice Nov 02 '17 at 00:11
  • 3
    @CRice _"No need for the async anonymous function."_ If OP is expecting a `Promise` to be returned and `await` to be used, `await` would cause error if `async` is not used at anonymous arrow function – guest271314 Nov 02 '17 at 00:28
  • This works, and you made me understand that a using `await` make a function to return a `promise` ! – realtebo Nov 04 '17 at 17:45
  • @Crice can you explain why it still works when `async await` are omitted on `addEnabledProperty`? – Saitama Jun 17 '18 at 07:23
  • 2
    @Saitama it works because you are wrapping a function that returns a Promise (and we know because you need to use 'await') with another function which still returns a Promise. And since the only argument of the anonymous async arrow is passed down to 'addEnabledProperty' we can therefore be pretty sure you *don’t* need to wrap it. It would be necessary if 'addEnabledProperty' accepted a second (or third) argument that we do not want to pollute with what is passed from `_.map`. – Pier Paolo Ramon Nov 05 '18 at 21:44
4

I found that I didn't have to put the async / await inside of the Promise.all wrapper.

Using that knowledge, in conjunction with lodash chain (_.chain) could result in the following simplified version of the accepted answer:

const responseJson = await Promise.all( _
                       .chain( response.json() )
                       .sortBy( 'number' )
                       .map( json => addEnabledProperty( json ) )
                       .value()
                     )
Javid Jamae
  • 8,741
  • 4
  • 47
  • 62
0

You can use Promise.all() to run all the promises in your array.

responseJson = await Promise.all(_.map(responseJson, (channel) => {
        return addEnabledProperty(channel);
    }));
Rodrigo Leite
  • 596
  • 5
  • 22
  • 4
    There is no need to declare this async and await the `addEnabledProperty`. Using `function(channel) { return addEnabledProperty(channel) }` would achieve the same result. For even further simplification: `_.map(responseJson, addEnabledProperty)` to avoid the needless wrapper function. – CRice Nov 02 '17 at 00:07
  • @Crice can you explain WHY it is not needed? – Saitama Jun 17 '18 at 07:22
  • 2
    It's not needed because all that an async function does is return a promise. You can skip that and simply return the `addEnabledProperty` promise directly. – Rodrigo Leite Jun 17 '18 at 20:14
0

How about using partial.js(https://github.com/marpple/partial.js)

It cover both promise and normal pattern by same code.

_p.map([1, 2, 3], async (v) => await promiseFunction());
junho
  • 3,603
  • 3
  • 18
  • 25
-1

If you want to iterate over some object, you can use keys first to get an array of keys and then just loop over your keys while awaiting for necessary actions.

This works when you want to wait until every previous iteration step is finished before getting into the next one.

Here is a consolidated asyncMap function that you can use for your object:

async function asyncMap(obj, cb) {
  const result = {};

  const keysArr = keys(obj);

  let keysArrLength = keysArr.length;
  while (keysArrLength-- > 0) {
    const key = keysArr[keysArrLength];

    const item = obj[key];

    // eslint-disable-next-line no-await-in-loop
    result[key] = await cb(item, key);
  }

  return result;
}

And then, for your example case:

responseJson = await asyncMap(responseJson, addEnabledProperty);

Otherwise use Promise.all like was proposed above to run all the iteration steps in parallel

HydraOrc
  • 657
  • 7
  • 15