16

I have a list of objects that I want to process. The object is passed to a promise function that does this and that and resolves back. The process could be instant or not, based on previously cached value. If there is already calculated value, it will resolve to it instantly. Else, it will calculate. Now the issue I am having is that the next object is passed to the promise before the first object's status is calcualted:

   let people = [ 
                {groupId: 1, name: 'Jessica Coleman', status: 'Unknown', id:1}
                {groupId: 1, name: 'Eric Tomson', status: 'Unknown', id:2}
                {groupId: 1, name: 'Samuel Bell', status: 'Unknown', id:3}

      ];

now I want to absolutely wait for the promise to resolve during loop even if the promise takes a minute to calculate on the very instance. All people with the same group have the same status. Hence, the promise checks if a group has already been calculated. If yes, returns it. Else, it calculates. and that's where the issue lies. Before Jessica 1 is finished, the other people are passed.

    people.map(function(person) {
   // return the promise to array
   this.calculatorService
    .getStatus(person)
    .then(function(res) {
      person.status = res;


    });
});
Sudarshana Dayananda
  • 5,165
  • 2
  • 23
  • 45
Wede Asmera Tseada
  • 513
  • 2
  • 4
  • 14
  • 1
    Does this answer your question? [Use async await with Array.map](https://stackoverflow.com/questions/40140149/use-async-await-with-array-map) – Ben Smith Dec 02 '19 at 11:08

4 Answers4

31

Array iterators like map or forEach don't work with promises because they don't know how to await a result. Use a simple for loop instead:

for (let person of people)
  person.status = await this.calculatorService.getStatus(person)

If you really want a "functional" way (and avoid explicit async/await), you can define a function similar to the bluebird's Promise.each:

Promise.each = function(ary, fn) {
    return ary.reduce((p, x) => p.then(() => fn(x)), Promise.resolve(null))
}

and apply it like this:

function setStatus(person) {
    return calculatorService
        .getStatus(person)
        .then(res => person.status = res);
}

Promise.each(people, setStatus).then(...)
georg
  • 211,518
  • 52
  • 313
  • 390
7

Make it work synchronously with async/await. (for..of would be better suited than .map in this case btw).

for (let person of people) {
   person.status = await this.calculatorService.getStatus(person);
})
Kees de Kooter
  • 7,078
  • 5
  • 38
  • 45
  • 2
    I'd recommend you never use `.forEach` for asynchronous work since there's no way to actually await until the loop finishes. A [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) or a simple `for` loop is far more appropriate. – nicholaswmin Dec 02 '19 at 11:12
1

you can try like this

let people = [ 
  {groupId: 1, name: 'Jessica Coleman', status: 'Unknown', id:1},
  {groupId: 1, name: 'Eric Tomson', status: 'Unknown', id:2},
  {groupId: 1, name: 'Samuel Bell', status: 'Unknown', id:3}
];


for (let person of people) {
  await this.calculatorService.getStatus(person).then(res => {
    person.status = res;
  });
}
nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
Yash Rami
  • 2,276
  • 1
  • 10
  • 16
  • You're not returning anything in your `Array.map`; even if you did, you can't eventually wait for an `Array.map` to finish async work. Also this is far more convoluted than it needs to be. Why are you wrapping a `Promise` in another `Promise`? – nicholaswmin Dec 02 '19 at 11:17
  • Yes it is convoluted but it is working as expected, I edited my answer now i am returning a updated value. thanks for pointing out my mistake :) – Yash Rami Dec 02 '19 at 11:21
  • All the mistakes I pointed out in my first comment are still there. You're also using `Array.map` as a loop. It's not really mapping anything. – nicholaswmin Dec 02 '19 at 11:22
  • You don't need to wrap `this.calculatorService.getStatus(person)` in another `Promise`. I'll edit your answer myself so I can show you. – nicholaswmin Dec 02 '19 at 11:27
  • but then it will wont work as far as i know we need to return the promise or we have to return promise in `this.calculatorService.getStatus()` function. – Yash Rami Dec 02 '19 at 11:29
  • then your way is valid :) – Yash Rami Dec 02 '19 at 11:31
  • 1
    here's an example of it working using a mock `getStatus` function: https://jsfiddle.net/ruegzowy/ – nicholaswmin Dec 02 '19 at 11:33
0

You could use a recursive function:

let people = [ 
    {groupId: 1, name: 'Jessica Coleman', status: 'Unknown', id:1},
    {groupId: 1, name: 'Eric Tomson', status: 'Unknown', id:2},
    {groupId: 1, name: 'Samuel Bell', status: 'Unknown', id:3}
];

function recCalculatorService(people) {
    if (!people || !people.length) {
        return;
    }
    this.calculatorService
        .getStatus(people.shift())
        .then(function(res) {
            person.status = res;
            recCalculatorService(people);
        });
}

// use people.slice() if you want to keep people array intact
recCalculatorService(people);
Adrian
  • 3,321
  • 2
  • 29
  • 46