typescript - Angular vänta på att en loop med löften ska köras innan nästa kodblock fortsätter


Jag försöker skicka data till en API-endpoint men av någon konstig anledning, som jag inte kan lista ut, skickas inte datan.

Nedan är metoden men när du granskar begäran.

  syncPatients(){  
    let patients = [];
    this.sqlite.create({name: 'criticare.db', location: 'default'})
    .then((db: SQLiteObject) =>{
      db.executeSql('SELECT * FROM patients where updated_at >= (?)',[this.settings.last_sync_time])
      .then((data) =>{
        if(data.rows.length > 0){
          //let patients = [];
          for(var i =0; i < data.rows.length; i++){
            var _patient = data.rows.item(i);
            //console.log(_patient);
            db.executeSql('SELECT * FROM wards WHERE id = (?)',[_patient.id])
            .then((ward_data)=>{
              if(ward_data.rows.length > 0){
                _patient.ward_id = ward_data.rows.item(0).online_ref;
                patients.push(JSON.stringify(_patient));
                //console.log(patients);
              }else{
                console.log("Ward not found");
              }              
            })            
          }//end of for loop
        }else{
          console.log('No patients to sync');
        }
      })
      .then(()=>{
          console.log(patients);         
          var headers = new Headers();
          headers.append("Accept", 'application/json');
          headers.append('Content-Type', 'application/json; charset=utf-8');
          headers.append('Authorization','Bearer '+ this.settings.token);
          let options = new RequestOptions({ headers: headers });
          let postParams = { patients: JSON.stringify(patients) };
          console.log(patients);
          this.http.post(this.api_url+"/patients/sync", postParams, options).subscribe(data => {
            let response = JSON.parse(data['_body']);
            if(response.status == "Ok"){
              response.result.forEach(element =>{
                db.executeSql('UPDATE patients SET online_ref = (?), updated_at = (?) WHERE id = (?)',[element.online_id, this.getCurrentTime(), element.id]).then(() => {
                  console.log('Updated patient ' + element.id)
                }).catch(e =>{
                  console.log(e);
                })
              });
            }else{
              console.log("Error syncing patients");
            }
          }) //end of post
      })
    })

  }

Problemet är i den sista .then(). I konsollloggarna före och efter jag gör let postParams={ patients: JSON.stringify(patients) }; visas att patients har rätt data, men när du inspekterar förfrågan är nyttolasten

 {
  "patients": ""
}

Vad missar jag eller gör fel?

Uppdatering Visade sig som @orodan och @jc-ford föreslog, att problemet var med att http-postingen kördes innan några löften löstes. Jag har omstrukturerat koden med deras förslag, infört async och await nedan och det fungerar utmärkt nu.

 syncPatients(){
  let patients = [];
  this.sqlite.create({name: 'criticare.db', location: 'default'})
  .then((db:SQLiteObject) => {
    db.executeSql('SELECT * FROM patients where updated_at >= (?)',[this.settings.last_sync_time])
    .then((data) =>{
      if(data.rows.length > 0){
        for(var i =0; i < data.rows.length; i++){
          patients.push(data.rows.item(i));           
        }
      }
      else{
        console.log('No patients to sync');
      }
      return patients;
    })
    .then(async (patients)=>{
      const patientsPromises = patients.map(async (patient) =>{
        const data = await db.executeSql('SELECT * FROM wards WHERE id = (?)',[patient.id])
         patient.ward_id = data.rows.item(0).online_ref
      })
      // new patients
      let all_promises = await Promise.all(patientsPromises);
      console.log(patients);
      var headers = new Headers();
      headers.append("Accept", 'application/json');
      headers.append('Content-Type', 'application/json; charset=utf-8');
      headers.append('Authorization','Bearer '+ this.settings.token);
      let options = new RequestOptions({ headers: headers });
      let postParams = { patients: JSON.stringify(patients) };
      this.http.post(this.api_url+"/patients/sync", postParams, options)
      .subscribe(data => {
        let response = JSON.parse(data['_body']);
        if(response.status == "Ok"){
          this.sqlite.create({name: 'criticare.db', location: 'default'})
          .then((db:SQLiteObject) =>{
            response.result.forEach(element =>{
              db.executeSql('UPDATE patients SET online_ref = (?), updated_at = (?) WHERE id = (?)',[element.online_id, this.getCurrentTime(), element.id])
              .then(() => {
                console.log('Updated patient ' + element.id)
              })
              .catch(e =>{
                console.log(e);
              })
            });
          })

        }else{
          console.log("Error syncing patients");
        }
      }) //end of subscribe
    })
  }) 
 }

Svar #1

Du har ett asynkront problem här: du väntar inte på att dina vårdinnehavares data ska vara här innan du gör ditt HTTP-anrop.

Eftersom du måste vänta på flera anrop till din databas innan du gör ditt HTTP-anrop, skulle jag råda att använda Promise.all vilket tillåter dig att vänta på att alla löften som skickats som en array-parameter ska slutföras innan du fortsätter med något. Det skulle se ut ungefär så här:

const dbCalls = [];
for (let i = 0; i < data.rows.length; i++) {
    const _patient = data.rows.item(i);
    dbCall = db.executeSql('SELECT * FROM wards WHERE id = (?)',[_patient.id]);
}

Promise.all(dbCalls)
   .then(dbResults => {
      dbResults.forEach(ward_data => {
          if (ward_data.rows.length > 0) {
             _patient.ward_id = ward_data.rows.item(0).online_ref;
             patients.push(JSON.stringify(_patient));
           } else {
             console.log("Ward not found");
           }
      });

      console.log(patients);         
      var headers = new Headers();
      headers.append("Accept", 'application/json');
      headers.append('Content-Type', 'application/json; charset=utf-8');
      headers.append('Authorization','Bearer '+ this.settings.token);
      let options = new RequestOptions({ headers: headers });
      let postParams = { patients: JSON.stringify(patients) };
      console.log(patients);
      this.http.post(this.api_url+"/patients/sync", postParams, options).subscribe(data => {
        let response = JSON.parse(data['_body']);
        if(response.status == "Ok"){
          response.result.forEach(element =>{
            db.executeSql('UPDATE patients SET online_ref = (?), updated_at = (?) WHERE id = (?)',[element.online_id, this.getCurrentTime(), element.id]).then(() => {
              console.log('Updated patient ' + element.id)
            }).catch(e =>{
              console.log(e);
            })
          });
        }else{
          console.log("Error syncing patients");
        }
      }) //end of post
  })

Och angående varför du såg dina patienter medan du felsökte: lita inte på console.log. Eftersom du hade ett asynkront problem, vid någon tidpunkt, avslutades dina databas-anrop och dina patienter blev fyllda. console.log gav dig den "senaste versionen" av dina patienter, men vid det ögonblick du försökte göra ditt http-anrop, var de ännu inte här ^^

PS: Jag använde lite ES6-syntax i mitt exempel, om du inte kan, byt bara ut pilfunktioner mot "function" och alla const och let mot "var".

Svar #2

Jag tror problemet ligger i dina löften. Se mina kommentarer nedan.

 syncPatients(){  
    let patients = [];
    this.sqlite.create({name: 'criticare.db', location: 'default'})
    .then((db: SQLiteObject) =>{
      db.executeSql('SELECT * FROM patients where updated_at >= (?)',[this.settings.last_sync_time])
      .then((data) =>{
        if(data.rows.length > 0){
          //let patients = [];
          for(var i =0; i < data.rows.length; i++){
            var _patient = data.rows.item(i);
            //console.log(_patient);


            //You're kicking off an asynchronous process here, but not chaining
            //its promise to the current promise.

            db.executeSql('SELECT * FROM wards WHERE id = (?)',[_patient.id])
            .then((ward_data)=>{
              if(ward_data.rows.length > 0){
                _patient.ward_id = ward_data.rows.item(0).online_ref;
                patients.push(JSON.stringify(_patient));
                //console.log(patients);
              }else{
                console.log("Ward not found");
              }              
            })            
          }//end of for loop
        }else{
          console.log('No patients to sync');
        }
      })
      .then(()=>{

          //This part executes before the executeSql() promises above return
          //so the patients.push() above hasn't happened yet.

          console.log(patients);         
          var headers = new Headers();
          headers.append("Accept", 'application/json');
          headers.append('Content-Type', 'application/json; charset=utf-8');
          headers.append('Authorization','Bearer '+ this.settings.token);
          let options = new RequestOptions({ headers: headers });
          let postParams = { patients: JSON.stringify(patients) };
          console.log(patients);
          this.http.post(this.api_url+"/patients/sync", postParams, options).subscribe(data => {
            let response = JSON.parse(data['_body']);
            if(response.status == "Ok"){
              response.result.forEach(element =>{
                db.executeSql('UPDATE patients SET online_ref = (?), updated_at = (?) WHERE id = (?)',[element.online_id, this.getCurrentTime(), element.id]).then(() => {
                  console.log('Updated patient ' + element.id)
                }).catch(e =>{
                  console.log(e);
                })
              });
            }else{
              console.log("Error syncing patients");
            }
          }) //end of post
      })
    })

  }

Försök att returnera executeSql()-löften inneslutna i $q.all() (eller jag tror Promise.all() om du är i Angular 2+-världen) från ditt första .then() för att kedja ihop löftena. Jag tror de fungerar i stort sett på samma sätt, men här är länkar till dokumentationen för var och en.

https://docs.angularjs.org/api/ng/service/$q#all

https://developer.mozilla.org/sv/docs/Web/JavaScript/Reference/Global_Objects/Promise/all