1

I have a certain promise chain in my code that looks like this:

  myPromise()
    .then(getStuffFromDb)
    .then(manipulateResultSet)
    .then(manipulateWithAsync)
    .then(returnStuffToCaller)

Now, in my manipulateWithAsync I'm trying to enhance my result set by calling the DB again, but it's not working as I expected, since while debugging i figured out the control moves to the next function which is the returnStuffToCaller

here's an idea of what's into my manipulateWithAsync function:

function manipulateWithAsync(rs) {
  return rs.map( async function whoCares(singleRecord) {
      let smthUseful = await getMoreData(singleRecord.someField);
      singleRecord.enhancedField = smthUseful;
      return singleRecord;
  })
}

I get the point of this behaviour: the map function does work as expected and the promise chain doesn't give a duck about it since it's not working with the awaits. Is there a way to allow my returnStuffToCaller function to wait till the async function did his job?

I also use bluebird and i tried to use coo-routine, so if you thing that it's a good solution I'll post my bluebird coo-routine failing code :)

Thanks!

Robdll
  • 5,865
  • 7
  • 31
  • 51
  • 6
    `return Promise.all(rs.map(...))` but it might help to `return` something from your map function, otherwise you're going to resolve with an array filled with `undefined` – Patrick Roberts Jul 03 '18 at 09:13
  • yeah man, i clearly return something in my map. i wrote the code on the fly while creating the post, thanks for pointing out, gonna edit right away – Robdll Jul 03 '18 at 09:19
  • But as @PatrickRoberts pointed out, you need to have `Promise.all`, otherwise the promise resolves with the array returned by `manipulateWithAsync`. – Thiago Barcala Jul 03 '18 at 09:22
  • 1
    Confirmed, just modified my code and it works properly. @Patrick if you can create an answer i'd be happy to approve it. – Robdll Jul 03 '18 at 09:23

3 Answers3

2

The problem is in using async/await with Array.map

This answer should help: https://stackoverflow.com/a/40140562/5783272

1

rs.map iterator jumps to the next element without waiting in each separate iteration. You need something like asyncMap You can use - https://github.com/caolan/async or either implement yourself

async function asyncMap(array, cb) {
  for (let index = 0; index < array.length; index++) {
      return await cb(array[index], index, array);
  }
}

*cb function must be async one

dima golovin
  • 262
  • 2
  • 8
1

Wrap your map with Promise.all return the Promise then await for the results wherever you call the manipulateWithAsync.

// MOCKS FOR DEMO
// Test data used as input for manipulateWithAsync
const testData = [
  { recordNumber: 1 },
  { recordNumber: 2 },
  { recordNumber: 3 }
];

// Mock function which returns Promises which resolve after random delay ranging from 1 - 3 seconds
const getMoreData = () =>
  new Promise(resolve => {
    const calledAt = Date.now();
    setTimeout(() => {
      resolve({
        usefulData: `Promise called at ${calledAt}`
      });
    }, Math.floor(Math.random() * 3000) + 1000);
  });

// SOLUTION / ANSWER
const manipulateWithAsync = async rs =>
  Promise.all(
    rs.map(async singleRecord => {
      const smthUseful = await getMoreData(singleRecord.someField);

      // Instead of manipulating original data,
      // which might cause some unwanted side effects going forward,
      // instead return new objects
      return { ...singleRecord, enhancedField: smthUseful };
    })
  );

await manipulateWithAsync(testData);