简体   繁体   中英

Promises and Q - Completing a long series of functions with a forEach

A little confused on how best to write this - hope this description is clear.

I have a forEach function in which I go through some JS object data, and for each item i execute a function:

found.forEach(function(item) {
      processData(item['userID']);
});

Within this processData function I am using a MongoDB find() call.

var processData = function(userIDSelected) {

    User.find( {_id: userIDSelected},
           {gender: 1, country:1}, function(req, foundUser) {
            processUserInfo(foundUser[0]['gender']);
    });
}

The issue I have is how do i wait for everything in forEach to complete considering that each call will be running processUserInfo in turn.

I've looked at using the Q library and Q.all however that doesnt work.

Is there a Q function that waits for everything in the long chain to complete?

Thanks

Q.all :

Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected.

or Q.allSettled :

Returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, ie become either fulfilled or rejected.

So you'd do three things:

  1. Modify processData and possibly your call to MongoDB so that you end up with processData returning a promise for the asynchronous operation. (Apologies, I'm not familiar with MongoDB. Alnitak says that with modern versions, if you don't supply a callback, MongoDB returns a promise (nice!). Otherwise, this question and its answers may help with returning a promise for a callback-based API.)

  2. Use map instead of forEach to get an array of the resulting promises.

  3. Use Q.all or Q.allSettled on that array of promises.

If User.find returns a promise when no callback is specified, #1 looks something like this:

var processData = function(userIDSelected) {

    return User.find(
           {_id: userIDSelected},
           {gender: 1, country:1}
    ).then(function(req, foundUser) { // <== Check these args with the MongoDB docs!
        return processUserInfo(foundUser[0]['gender']);
    });
};

If not, you can do it yourself with Q.defer :

var processData = function(userIDSelected) {
    var d = Q.defer();
    User.find(
           {_id: userIDSelected},
           {gender: 1, country:1},
           function(req, foundUser) {
        processUserInfo(foundUser[0]['gender']);
        d.resolve(/*...data could go here...*/); // You'd use d.reject() if there were an error
    });
    return d.promise;
};

Then here's what 2 and 3 look like:

Q.all(found.map(function(item) { // Or Q.allSettled
    return processData(item);
}))
.then(...)
.catch(...);

If processData only ever uses its first argument (ignoring any extra ones), you can ditch the intermediary function:

Q.all(found.map(processData)) { // Or Q.allSettled
.then(...)
.catch(...);

...but only if processData ignores extra args, as map will pass it three (the value, its index, and the array).

The way you coded I can assume that userID is an ObjectId from MongoDB.

If that is the case, this is going to work as long as found is not empty, otherwise your users would wait for the server response forever.

processData(
  // Retrieve an object 
  // {
  //   $in: [ObjectId, ObjectId, ...]
  // }
  // 
  found.reduce(
    function(query, user) {
      return query.$in.push(user.userID), query;
    },
    {$in: []}
  )
);

You can have more info about the $in operator here .

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