0

Consider these two functions when i call getStatusAll(data)-

data=[[],['1'],['2'],['3']];

async function getStatusAll(data) {
    console.log("In getStatusAll");
  try{
    let statusPromiseArray =  data.map(async(value) => { 
      result= await this.fetchStatusDBs(value);
      return result;
    });
    statusResolvedArray= await Promise.all(statusPromiseArray)
    return statusResolvedArray;
  }catch(err){
    throw(err);
  }
}

async function fetchStatusDBs(data) {
    console.log("In fetchStatusDBs");
  try{
      //fetch status in dvf_req_id for an dvf_req_id
      if(data.length==0){
        console.log("1");
        dvfStatus = await Promise.resolve("Disabled");
        console.log("2");
        trainingStatus = await Promise.resolve("Disabled");
        console.log("3");
        inferenceStatus = await Promise.resolve("Disabled");
      }
      else {
        console.log("4");
        dvfStatus = await Promise.resolve("Enabled");
        console.log("5");
        trainingStatus = await Promise.resolve("Enabled");
        console.log("6");
        inferenceStatus = await Promise.resolve("Enabled");
      }
      return [dvfStatus,trainingStatus,inferenceStatus];
  }catch(err){
    throw(err);
  }
}

I am trying to resolve multiple Promises within a Promise.all but the results is unexpected. Actual Output-

In getStatusAll
In fetchStatusDBs
1
In fetchStatusDBs
4
In fetchStatusDBs
4
In fetchStatusDBs
4
2
5
5
5
3
6
6
6
[["Enabled","Enabled","Disabled"],["Enabled","Enabled","Enabled"],["Enabled","Enabled","Enabled"],["Enabled","Enabled","Enabled"]]

Expected Output-

In getStatusAll
inside map
In fetchStatusDBs
1
2
3
inside map
In fetchStatusDBs
4
5
6
inside map
In fetchStatusDBs
4
5
6
inside map
In fetchStatusDBs
4
5
6
[["Disabled","Disabled","Disabled"],["Enabled","Enabled","Enabled"],["Enabled","Enabled","Enabled"],["Enabled","Enabled","Enabled"]]



But changing fetchStatusDBs like this returns output in the correct format.

async function fetchStatusDBs(data) {
    console.log("In fetchStatusDBs");
  try{
      if(data.length==0){
        dvfStatus = "Disabled";
        trainingStatus = "Disabled";
        inferenceStatus = "Disabled";
      }
      else {
        dvfStatus = "Enabled";
        trainingStatus = "Enabled";
        inferenceStatus = "Enabled";
      }
      return [dvfStatus,trainingStatus,inferenceStatus];
  }catch(err){
    throw(err);
  }
}

Can somebody help me out?

Brett Zamir
  • 14,034
  • 6
  • 54
  • 77
  • 2
    You don't declare `dvfStatus` and so on, so they are either "shared" within a single outer scope (if you declare them there), or implicitly global variables (if you fail to declare them anywhere - which btw is very bad practice). So as each Promise runs "concurrently" (not actually, but it's as well to think of it as if they do), the setting and reading of these variables overlaps with each other, in ways that aren't necessarily easy to predict. – Robin Zigmond Apr 30 '21 at 18:57

2 Answers2

1

You have several misunderstandings about async-await

async function getStatusAll(data) {
    console.log("In getStatusAll");
  try{
    let statusPromiseArray =  data.map(async(value) => { // map is sync
      result= await this.fetchStatusDBs(value); // global result
      return result; // return-await anti-pattern
    });
    statusResolvedArray= await Promise.all(statusPromiseArray) // global
    return statusResolvedArray; // return-await anti-pattern
  }catch(err){  // catch-throw anti-pattern
    throw(err);
  }
}

All of that can be written as -

function getStatusAll(data) {
  return Promise.all(data.map(v => this.fetchStatusDBs(v)))
}

And any error will automatically bubble up. No need to catch and re-throw. This will do all the fetches in parallel. You could do the requests in serial if you wanted. This is shown to demonstrate proper use of await in async -

async function getStatusAll(data) {
  const result = []
  for (const value of data)
    result.push(await this.fetchStatusDBs(value))
  return result
}
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Got it but if i am using the result of await in code below it. Then I should write try and catch right? – Ujjwal Madan May 01 '21 at 06:57
  • ``` async function getModelsSummary(orgId){ var query="SELECT objective,uid,last_updated_time FROM `model_enabled_status` where org_id = ? and is_enabled='1'"; console.log("In service getSummaryDetails"); try{ queryOutputList = await dObject.query(query,[orgId]); reqIDsList = await serviceHelper.getReqIDs(queryOutputList[0]); statusList = await serviceHelper.getStatusAll(reqIDsList); console.log("\n response Body:" + JSON.stringify(statusList)); return statusList; }catch(err){ throw(err); } } ``` – Ujjwal Madan May 01 '21 at 06:58
  • @UjjwalMadan No. You should only write `try { … }` if your `catch` block does something other than just rethrowing the error. (Notice this has nothing to do with async/await, it's the same for synchronous code). – Bergi May 01 '21 at 11:21
  • That's right. If you ever see yourself writing `... catch(err) { throw error }` simply remove the entire `try`/`catch` block. Same goes for `return await someFunc()`, in an `async` function it is the same as `return someFunc()` – Mulan May 01 '21 at 15:12
0

Your main problem is that the status variables in fetchStatusDBs are undeclared and therefore implicitly global. Since your code is making multiple concurrent calls, which are interleaved at the await suspension points, they affect each other - a classic race condition. To fix this, just declare the variables with let or var.

const data=[[],['1'],['2'],['3']];

async function getStatusAll(data) {
    console.log("In getStatusAll");
    let statusPromiseArray = data.map(this.fetchStatusDBs, this);
    return Promise.all(statusPromiseArray);
}

async function fetchStatusDBs(data) {
    console.log("In fetchStatusDBs");
    let dvfStatus, trainingStatus, inferenceStatus;
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //fetch status in dvf_req_id for an dvf_req_id
    if (data.length==0) {
        console.log("1");
        dvfStatus = await Promise.resolve("Disabled");
        console.log("2");
        trainingStatus = await Promise.resolve("Disabled");
        console.log("3");
        inferenceStatus = await Promise.resolve("Disabled");
    } else {
        console.log("4");
        dvfStatus = await Promise.resolve("Enabled");
        console.log("5");
        trainingStatus = await Promise.resolve("Enabled");
        console.log("6");
        inferenceStatus = await Promise.resolve("Enabled");
    }
    return [dvfStatus, trainingStatus, inferenceStatus];
}

This will have the desired results (return value). To also get the expected output from the logs, you cannot use map with an asynchronous function and then wait for all the promises with Promise.all, but you need to make the calls in sequence:

async function getStatusAll(data) {
    console.log("In getStatusAll");
    const statusArray = [];
    for (const value of data) {
//  ^^^^^^^^^^^^^^^^^^^^^^^^^
        statusArray.push(await this.fetchStatusDBs(value));
//                       ^^^^^
    }
    return statusArray ;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375