简体   繁体   中英

Returning referenced data in promise chain

Say I need to find some students:

var promise = Student.find({..}).exec();

And then I need to access documents that reference them individually and embed them to return:

promise.then(function(students) {
  var promises = [];
  students.forEach(function(student) {
    var groupPromise = Group.find({ studentRef: student._id }).exec();
    groupPromise.then(function(group) {
      ...
      student.embed = group;
      return student;
    });
    promises.push(groupPromise);
  });
  return Promise.all(promises);
}).then(function(result) {
   // expect result to be array of students, with { embed: group }
   // instead result is array of groups
});

I've come a long way in figuring out promises, but I'm missing something. I would expect the groupPromise.then(...) to execute before Promise.all(...) could be evaluated. As that is clearly not the case, I need a way to ensure that each student has the right group embedded.

The promise you put in the array and pass to Promise.all() needs to be the returned result from groupPromise.then() , not groupPromise itself. Because you are using groupPromise , you aren't seeing the result of groupPromise.then() .

Remember every .then() returns a new promise and it's that new promise that tracks the .then() result. You weren't passing that new promise to Promise.all() so your results did not show the results of what was happening inside the .then() .

Change to this:

promise.then(function(students) {
  var promises = [];
  students.forEach(function(student) {
    var p = Group.find({ studentRef: student._id }).exec().then(function(group) {
      ...
      student.embed = group;
      return student;
    });
    promises.push(p);
  });
  return Promise.all(promises);
}).then(function(result) {
   // result is array of students, with { embed: group }
});

FYI, this is a bit more streamlined when using .map() instead of .forEach() :

promise.then(function(students) {
    return Promise.all(students.map(function(student) {
        return Group.find({ studentRef: student._id }).exec().then(function(group) {
            ...
            student.embed = group;
            return student;
        });
    }));
}).then(function(result) {
   // result is array of students, with { embed: group }
});

And, even a little simpler using Promise.map() from Bluebird's promise library :

promise.then(function(students) {
    return Promise.map(students, function(student) {
        return Group.find({ studentRef: student._id }).exec().then(function(group) {
            ...
            student.embed = group;
            return student;
        });
    });
}).then(function(result) {
   // result is array of students, with { embed: group }
});    

Basically theres just one thing wrong:

groupPromise.then(function(group) {
  ...
  student.embed = group;
  return student;
})
.then(function(student){
  //this is what you want
});

groupPromise.then(function(group){
  //this is what you do
});

groupPromise always resolves to group . May add the groupPromise.then Promise that resolves to student to your Promise.all:

promises.push(groupPromise.then(function(group) {
  ...
  student.embed = group;
  return student;
}));

How i would do the whole thing:

promise.then(function(students) {
    return Promise.all(students.map(function(student) {
        return Group.find({ studentRef: student._id }).exec().then(function(group) {
            ...
            student.embed = group;
            return student;
        });
    }));
}).then(function(result) {
// expect result to be array of students, with { embed: group }
});

I have discovered that moving the promise from a variable declaration works:

var promise = Student.find({..}).exec();
promise.then(function(students) {
  var promises = [];
  students.forEach(function(student) {
    promises.push(
      Group.find({ studentRef: student._id }).exec()
        .then(function(group) {
          ...
          student.embed = group;
          return student;
        })
    );
  });
  return Promise.all(promises);
}).then(function(result) {
  // now results is an array of students with { embed: group }}       
});

I must admit, I don't know why it works. Anyone?


Edit: Solutions found. As pointed out by you clever people, the importance is in which part of the promise is returned. The promise pushed into the promises array needs to be the promise returned by the .then() . These methods is really just a syntactical analogues of this, but I thought they might be illuminating for future readers.

Method One - push the promise.then() entirely:

var promises = [];
students.forEach(function(student) {
  promises.push(Group.find().exec().then(function(group) {
     student.embed
     return student;
  });
});
return Promise.all(promises); 

Not particularly pretty.

Method Two - Assign the promise.then() to another variable:

var promises = [];
students.forEach(function(student) {
  var groupPromise = Group.find().exec();
  var embedPromise = groupPromise.then(function(group) {
     student.embed
     return student;
  });
  promises.push(embedPromise);
});
return Promise.all(promises);

A little prettier than method one, but it's not really any easier to read.

Method Three - As suggested by jfriend00 and Jonas w, .map() is elegant:

promise.then(function(students) {
  return Promise.all(students.map(function(student) {
    return Group.find({ studentRef: student._id }).exec().then(function(group) {
      ...
      student.embed = group;
      return student;
    });
  }));
}).then(function(result) {
  // expect result to be array of students, with { embed: group }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM