Search code examples
node.jspseudocodeqloopbackjs

Q.allSettled not returning all the results


I'm trying to use Q.allSettled in NodeJS (LoopbackJS) for the following scenario.

As an input to my method (REST API) I get an array of objects. Each of these objects internally has 2 objects: ObjA & ObjB.

For each item in the array:

If ObjA exists in the database get its ID and send mail #1

If it doesn't exist then insert ObjA and then get its ID and send mail #2.

Set ObjA.ID in ObjB and save ObjB.

Once all the objects are saved and emails are sent, then send the response of REST API call. If any of the previous tasks have failed, add the error details in the response.

Here is the pseudo code:

myModel.myMethod = function(input, cb){
  var defResp = Q.defer();
  var promises = [];
  try {
    var defObjAList = Q.defer();
    promises.push(defObjAList.promise);

    getObjAIfItExists(input).done(function(inputWIds) { // input[] with IDs populated
      inputWIds.forEach(function(item){
        var defObjA = Q.defer();

        if(item.objA.id){ // objA already exists in DB
          var options = { ... }; // options for sending mail #1
          promises.push(Q.ninvoke(Email, "send", options)); // using loopback Email which internally uses nodemailer
          defObjA.resolve(item.objA.id);
        } else {
          Q.ninvoke(ObjA, "save", item.objA).done(function (savedA) {
            var options = { ... }; // options for sending mail #2
            promises.push(Q.ninvoke(Email, "send", options)); // using loopback Email which internally uses nodemailer
            console.log(promises.length); // prints 3
            defObjA.resolve(savedA.id);
          }, function(err){
            defResp.reject(err);
          });
        }

        var defObjB = Q.defer();
        promises.push(defObjB.promise);

        defObjA.promise.done(function(objAId){
          item.objB.objAId = objAId;
          promises.push(Q.ninvoke(ObjB, "save", item.objB));
          console.log(promises.length); // prints 4
        }, function(err){
          defResp.reject(err);
        });

        console.log(promises.length); // prints 2
        defObjAList.resolve("Process Complete");
      }); // inputWIds.forEach
    }, function(err){
      defResp.reject(err);
    }); //getObjAIfItExists

    console.log(promises.length); // prints 1
    Q.allSettled(promises).done(function (results) {
      console.log(results.length); // prints 1
      console.log(JSON.stringify(results)); // prints result[] with single item
      var response = {};
      response.errors = [];
      // iterate on results and check if any promise was failed, if yes add the reason to errors array
      defResp.resolve(response);
    });

  } catch (err) {
    defResp.reject(err);
  }
  return defResp.promise.nodeify(cb);
}

For the testing purpose my input array only contains one item. So the total number of promises that get added to promises[] are 4. But in spite of that the result array contains only 1 item.

My code is working for normal case, but if there is an error e.g. while sending mail, I need to send it in response. That is not working because the results array doesn't contain the email sending promise's output.

Can someone tell me what am I doing wrong? And if I need to handle it in some other way?


Solution

  • I've found out a way to handle the scenario described above. But I'm still open for alternate approaches.

    Instead of resolving defObjAList after adding 2nd promise to promises array, I'm waiting till all the promises are added to the array.

    Then inside the allSettled handler (which was monitoring only 1 promise), I again call Q.allSettled by passing promises array again (which now contains 4 promises). Now this second handler for allSettled gets the results array with 4 items for all the promises that were added.

    Alternatively I could simple wait for defObjAList promise to fulfill and then call Q.allSettled for rest of the promises. This will be better in terms of performance. But for the time being I've kept 2 calls to Q.allSettled.

      :
      :
      inputWIds.forEach(function(item, ind){ // added ind param
    
        // handle ObjA
    
        var defObjB = Q.defer();
        promises.push(defObjB.promise);
    
        defObjA.promise.done(function(objAId){
          item.objB.objAId = objAId;
          promises.push(Q.ninvoke(ObjB, "save", item.objB));
          console.log(promises.length); // prints 4
          if(ind == inputWIds.length-1){ // check if its the last iteration
            defObjAList.resolve("Process Complete");
          }
        }, function(err){
          defResp.reject(err);
        });
    
        console.log(promises.length); // prints 2
        // defObjAList.resolve("Process Complete"); - removed from here
      }); // inputWIds.forEach
    
      :
      :
    
    Q.allSettled(promises).done(function (resultsOld) {
      Q.allSettled(promises).done(function (results) {
        console.log(results.length); // prints 4
        // handle results array and send response
      });
    });