0

In the following code, expiringContentsAllBU is populated within async/await function getOnlyContentWhatsNewReport which is inside the array.map(), but expiringContentsAllBU becomes empty when accessing it outside the businessUnits.map() function.

const getExpiringContents = async () => {
  let businessUnits = Object.keys(contentFulSpaces);  
  let expiringContentsAllBU = [];
  businessUnits.map( async (bu) => {
      await getOnlyContentWhatsNewReport(bu, (err, result) => { 
        if(result) {
          let expiringContentsByBU = {};
          expiringContentsByBU['businessUnit'] = bu;
          expiringContentsByBU['expiringContents'] = result;
          expiringContentsAllBU.push(JSON.parse(JSON.stringify(expiringContentsByBU)));
        } else console.log('No expiring contents found for WhatsNewSection');
      })
    });
    console.log('result', expiringContentsAllBU);
}
9
  • 2
    map is synchronous method, you can't use async code in callbacks Commented Apr 24, 2019 at 16:19
  • Why are you using using map() for this? Why not just normal iteration? Commented Apr 24, 2019 at 16:20
  • @deathangel908 You can, but the result will be an array of promises. If you'd use Promise.all, that's usually even desirable. Commented Apr 24, 2019 at 16:20
  • getOnlyContentWhatsNewReport does not appear to return a promise, since it takes a callback. That means you cannot await anything. Commented Apr 24, 2019 at 16:21
  • @Bergi, I didn't say that js won't compile. in Example above map makes no sense, it's better to use forEach Commented Apr 24, 2019 at 16:25

4 Answers 4

2

var getOnlyContentWhatsNewReport = Promise.resolve(123);

const getExpiringContents = async () => {
  let businessUnits = [{ id: 1 }, { id: 2 }, { id: 3 }];  
  const expiringContentsAllBU = await Promise.all(businessUnits.map(async (bu) => {
      return getOnlyContentWhatsNewReport.then(respBu => {
        bu.businessUnit = respBu;
        return bu;
      }).catch((err) => {
        console.log('No expiring contents found for WhatsNewSection');
        return null;
      });
   }));
   console.log('result', expiringContentsAllBU);
}

getExpiringContents();

You have to wait until the map completes and all callbacks are done. The console.log and the subsequent code blocks will be executed before your map completes, so

const getExpiringContents = async () => {
  let businessUnits = Object.keys(contentFulSpaces);  
  const expiringContentsAllBU = await Promise.all(businessUnits.map(async (bu) => {
      return getOnlyContentWhatsNewReport(bu, (err, result) => { 
        if(result) {
          let expiringContentsByBU = {};
          expiringContentsByBU['businessUnit'] = bu;
          expiringContentsByBU['expiringContents'] = result;
          return JSON.parse(JSON.stringify(expiringContentsByBU);
        } else {
          console.log('No expiring contents found for WhatsNewSection');
          return null;
        }
      })
   }));
   console.log('result', expiringContentsAllBU);
}
Sign up to request clarification or add additional context in comments.

2 Comments

But it prints undefined
@Prem, I don't know what you do inside getOnlyContentWhatsNewReport, but I have edited my answer and have posted a snippet to show how we can do it. The return is for inner function, you have to return the inner function response to main function to see the result.
1

As map is not aware of async functions you need to use something that is. One example is the Bluebird Promise.map equivalent:

const getExpiringContents = async () => {
  let businessUnits = Object.keys(contentFulSpaces);  

  // Use Promise.map here to convert each businessUnit entry into a record
  let expiringContentsAllBU = await Promise.map(businessUnits, async (bu) => {
    await getOnlyContentWhatsNewReport(bu, (err, result) => { 
      if (!result) {
        console.log('No expiring contents found for WhatsNewSection');
        return;
      }

      let expiringContentsByBU = {};
      expiringContentsByBU['businessUnit'] = bu;
      expiringContentsByBU['expiringContents'] = result;

      return JSON.parse(JSON.stringify(expiringContentsByBU));
    })
  });

  // As this may contain undefined elements, remove those
  expiringContentsAllBU = expiringContentsAllBU.filter(e => e);

  console.log('result', expiringContentsAllBU);
}

You could flatten this code a bit more if you made getOnlyContentWhatsNewReport return a promise as it should instead of using a callback method. async won't wait on callback methods so be sure that also returns a promise or this code won't wait properly.

A fully promisized version that's refactored a litlte more looks like this:

const getExpiringContents = async () => {
  let businessUnits = Object.keys(contentFulSpaces);  

  let expiringContentsAllBU = await Promise.map(businessUnits, async businessUnit => {
    let expiringContents = await getOnlyContentWhatsNewReport(businessUnit);

    if (!expiringContents) {
      console.log('No expiring contents found for WhatsNewSection');
      return;
    }

    // Choosing names that match the output means you can use the destructuring operator
    let expiringContentsByBU = {
      businessUnit,
      expiringContents
    };

    // A more formalized "clone" function could help here.
    return JSON.parse(JSON.stringify(expiringContentsByBU));
  });

  // As this may contain undefined elements, remove those
  expiringContentsAllBU = expiringContentsAllBU.filter(e => e);

  console.log('result', expiringContentsAllBU);
}

Comments

0

You can either change your .map() to a for loop.

Or in your .map() function, return promises. Then you can call await Promise.all(promiseArray)

Comments

0

Using async with some lodash function for sanity will help -

getExpiringContents = async() => {
  let businessUnits = Object.keys(contentFulSpaces);

  let expiringContentsAllBU = await Promise.map(businessUnits, async businessUnit => {
    let expiringContents = await getOnlyContentWhatsNewReport(businessUnit);

    if (_.isEmpty(expiringContents)) {
      console.log('No expiring contents found for WhatsNewSection');
      return;
    }

    // Choosing names that match the output means you can use the destructuring operator
    let expiringContentsByBU = {
      businessUnit,
      expiringContents
    };

    // A more formalized "clone" function could help here.
    return _.cloneDeep(expiringContentsByBU);
  });

  // As this may contain undefined elements, remove those
  expiringContentsAllBU = _.compact(expiringContentsAllBU);

  console.log('result', expiringContentsAllBU);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.