Prem
Prem

Reputation: 5987

async/await doesn't return the built array to the parent array.map()

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);
}

Upvotes: 0

Views: 209

Answers (4)

swaroop ghosh
swaroop ghosh

Reputation: 11

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);
}

Upvotes: 0

Avanthika
Avanthika

Reputation: 4182

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);
}

Upvotes: 2

tadman
tadman

Reputation: 211580

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);
}

Upvotes: 1

d-_-b
d-_-b

Reputation: 23171

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)

Upvotes: 0

Related Questions