1

Im new to async/await. when Im calling the function, the first console.log(newArr) prints the array elements but the second one is empty array and when I return the array that is empty too. Can somebody please help me where Im going wrong.

const prefixPM = 'PM';
    const decryptComment = (comment) => {
          const data = decrypt(comment).then(function (result) {
            const buf2 = Buffer.from(result, 'base64').toString('ascii');
            return buf2;
          });
          return data;
        };
    
const pmData = await queryInterface.sequelize.query(
      `select ram.id, ra.name from rater_attributes ra
      inner join rater_attribute_mappings ram
      on ra.id = ram.raterAttributeId
      inner join attributes a
      on a.id = ram.by
      where a.name='Project Manager'`,
      { raw: true, type: Sequelize.QueryTypes.SELECT },
    );

const createPMAttributes = (ratingId, rating) => {
      const newArr = [];
      pmData.map(async (data) => {
        const attribute = `${prefixPM}${data.name}`;

        let pmComment = rating.PMComment
          ? await decryptComment(rating.PMComment)
          : null;
        pmComment = JSON.parse(pmComment);
        newArr.push({
          ratingId: ratingId,
          raterAttributeMappingId: data.id,
          rating: rating[`${attribute}`],
          comment:
            pmComment && pmComment[`${attribute}Comment`] ? pmComment[`${attribute}Comment`] : null,
          createdAt: rating.createdAt,
          updatedAt: rating.updatedAt,
        });
      console.log(newArr) -- this prints the newArr elements
      });
      console.log(newArr); -- this prints empty arr
return newArr 
    };
habiba
  • 321
  • 3
  • 12

1 Answers1

2

.map() is not async-aware so it runs all the iterations of the loop in parallel starting the second iteration as soon as the await is hit). Thus, your second console.log() is trying to log newArr BEFORE any of the callbacks have finished and thus before they've put their data into newArr.

Here's the sequencing of what happens.

  1. You call pmData.map()
  2. It grabs the first element in the pmData array and calls the callback
  3. The callback does await decryptComment(...) which causes the callback to pause and immediately return a promise
  4. .map() gets that promise and accumulates it for the return value from .map()
  5. .map() then grabs the next value in the pmData array and calls the callback again, even though the first one is still waiting on the await decryptComment()

So, you end up running all of them in parallel and your .map() finishes BEFORE any of the callbacks have finished.

The solution is to either use await Promise.all() on the returned array of promises if you want these to run in parallel or switch to a for loop which will pause the loop when the await is hit.

Here's a solution while still running all the calls in parallel. This returns a promise that resolves to an array of your values.

const createPMAttributes = (ratingId, rating) => {
    return Promise.all(pmData.map(async (data) => {
        const attribute = `${prefixPM}${data.name}`;

        let pmComment = rating.PMComment ?
            await decryptComment(rating.PMComment) :
            null;
        pmComment = JSON.parse(pmComment);
        return {
            ratingId: ratingId,
            raterAttributeMappingId: data.id,
            rating: rating[`${attribute}`],
            comment: pmComment && pmComment[`${attribute}Comment`] ? pmComment[
                `${attribute}Comment`] : null,
            createdAt: rating.createdAt,
            updatedAt: rating.updatedAt,
        };
    }));
};

And, here's a solution using a for loop that runs the operations in sequence:

const createPMAttributes = async (ratingId, rating) => {
    const newArr = [];
    for (let data of pmData) {
        const attribute = `${prefixPM}${data.name}`;

        let pmComment = rating.PMComment ?
            await decryptComment(rating.PMComment) :
            null;
        pmComment = JSON.parse(pmComment);
        newArr.push({
            ratingId: ratingId,
            raterAttributeMappingId: data.id,
            rating: rating[`${attribute}`],
            comment: pmComment && pmComment[`${attribute}Comment`] ? pmComment[
                `${attribute}Comment`] : null,
            createdAt: rating.createdAt,
            updatedAt: rating.updatedAt,
        });
    }
    return newArr;
};

Here's a general hint. Only use .map() when you actually want to use the returned array that it generates. If all you're going to do is use it to iterate the array, then use a for loop instead as it is much more flexible and is async aware.

And, if you're making a callback to .map() as async because you're using await, then you will be running all your operations in parallel and the only way to know when it's done is to use Promise.all() on the array of promises that your callbacks generated. Remember, every async function returns a promise - always. So, let resultArr = someArr.map(async => {} {}) will always generate an array of promises that tells you when things are actually done.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thank you for you answer. I made the changes but when I call the function const arr = await createPMAttributes(pmRatingId, rating); console.log(arr); it prints [ undefined, undefined, undefined, undefined, undefined, undefined, undefined ] and not the array elements – habiba May 30 '21 at 19:57
  • @habiba - That looks like you don't have all the changes in my version. If you're using my first version, then see where my `return` is. – jfriend00 May 30 '21 at 19:59
  • but I have two functions like this one for PMattributes and another for archAttributes and I want to push the ratings from both the functions into a single arr and then iterate through that array and insert the objects into a databse, do you know how i can do that? – habiba May 30 '21 at 20:05
  • @habiba - I don't understand. Do you want to combine your two functions into one function that generates one result or you you have two separate functions and you get an array of results from each and you want to combine those two arrays? FYI, this sounds a bit like a new question. – jfriend00 May 30 '21 at 20:07
  • yeah that is right I have two separate functions and I get an array of results from each and I want to store those results into a variable so that I can iterate through that variable, yeah it does sound like a new question, but I would appreciate if you could help out and definitely, marking you answer as the correct one :) – habiba May 30 '21 at 20:09
  • @habiba - Well, you can get two arrays and then combine the properties from each array. – jfriend00 May 30 '21 at 21:02
  • can you please help answer another one of my question at stack overflow of async await. I’m really stuck, here is the link https://stackoverflow.com/questions/67769801/my-array-returned-prints-same-objects-multiple-times-using-await-promise-all-wi?noredirect=1#comment119786613_67769801 – habiba May 31 '21 at 14:48