Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js: How to run asynchronous code sequentially

I have this chunk of code

User.find({}, function(err, users) {
    for (var i = 0; i < users.length; i++) {
        pseudocode
        Friend.find({
            'user': curUser._id
        }, function(err, friends) * * ANOTHER CALLBACK * * {
            for (var i = 0; i < friends.length; i++) {
                pseudocode
            }
            console.log("HERE I'm CHECKING " + curUser);
            if (curUser.websiteaccount != "None") {
                request.post({
                    url: 'blah',
                    formData: blah
                }, function(err, httpResponse, body) { * * ANOTHER CALLBACK * *
                        pseudocode
                    sendMail(friendResults, curUser);
                });
            } else {
                pseudocode
                sendMail(friendResults, curUser);
            }
        });
        console.log("finished friend");
        console.log(friendResults);
        sleep.sleep(15);
        console.log("finished waiting");
        console.log(friendResults);
    }
});

There's a couple asynchronous things happening here. For each user, I want to find their relevant friends and concat them to a variable. I then want to check if that user has a website account, and if so, make a post request and grab some information there. Only thing is, that everything is happening out of order since the code isn't waiting for the callbacks to finish. I've been using a sleep but that doesn't solve the problem either since it's still jumbled.

I've looked into async, but these functions are intertwined and not really separate, so I wasn't sure how it'd work with async either.

Any suggestions to get this code to run sequentially?

Thanks!

like image 280
user3340037 Avatar asked Dec 31 '14 17:12

user3340037


People also ask

Is async await sequential?

Async Await makes execution sequential This is because it is happening in sequence. Two promises are returned, both of which takes 50ms to complete. The second promise executes only after the first promise is resolved. This is not a good practice, as large requests can be very time consuming.

How do you handle multiple asynchronous calls in NodeJS?

In order to run multiple async/await calls in parallel, all we need to do is add the calls to an array, and then pass that array as an argument to Promise. all() . Promise. all() will wait for all the provided async calls to be resolved before it carries on(see Conclusion for caveat).


3 Answers

I prefer the promise module to q https://www.npmjs.com/package/promise because of its simplicity

var Promises = require('promise');
var promise = new Promises(function (resolve, reject) {
    // do some async stuff
    if (success) {
        resolve(data);
    } else {
        reject(reason);
    }
});
promise.then(function (data) {
    // function called when first promise returned
    return new Promises(function (resolve, reject) {
        // second async stuff
        if (success) {
            resolve(data);
        } else {
            reject(reason);
        }
    });
}, function (reason) {
    // error handler
}).then(function (data) {
    // second success handler
}, function (reason) {
    // second error handler
}).then(function (data) {
    // third success handler
}, function (reason) {
    // third error handler
});

As you can see, you can continue like this forever. You can also return simple values instead of promises from the async handlers and then these will simply be passed to the then callback.

like image 188
unobf Avatar answered Nov 10 '22 08:11

unobf


I rewrote your code so it was a bit easier to read. You have a few choices of what to do if you want to guarantee synchronous execution:

  1. Use the async library. It provides some helper functions that run your code in series, particularly, this: https://github.com/caolan/async#seriestasks-callback

  2. Use promises to avoid making callbacks, and simplify your code APIs. Promises are a new feature in Javascript, although, in my opinion, you might not want to do this right now. There is still poor library support for promises, and it's not possible to use them with a lot of popular libraries :(

Now -- in regards to your program -- there's actually nothing wrong with your code at all right now (assuming you don't have async code in the pseucode blocks). Your code right now will work just fine, and will execute as expected.

I'd recommend using async for your sequential needs at the moment, as it works both server and client side, is essentially guaranteed to work with all popular libraries, and is well used / tested.

Cleaned up code below

User.find({}, function(err, users) {
  for (var i = 0; i < users.length; i++) {
    Friend.find({'user':curUser._id}, function(err, friends) {
      for (var i = 0; i < friends.length; i++) {
        // pseudocode
      }
      console.log("HERE I'm CHECKING " + curUser);
      if (curUser.websiteaccount != "None") {
        request.post({ url: 'blah', formData: 'blah' }, function(err, httpResponse, body) {
          // pseudocode
          sendMail(friendResults, curUser);
        });
      } else {
        // pseudocode
        sendMail(friendResults, curUser);
      }
    });

    console.log("finished friend");
    console.log(friendResults);
    sleep.sleep(15);
    console.log("finished waiting");
    console.log(friendResults);
  }
});
like image 26
rdegges Avatar answered Nov 10 '22 08:11

rdegges


First lets go a bit more functional

var users = User.find({});

users.forEach(function (user) {
  var friends = Friend.find({
    user: user._id
  });
  friends.forEach(function (friend) {
      if (user.websiteaccount !== 'None') {
         post(friend, user);
      }
      sendMail(friend, user);
  });
});

Then lets async that

async.waterfall([
  async.apply(Users.find, {}),
  function (users, cb) {
    async.each(users, function (user, cb) {
      async.waterfall([
        async.apply(Friends.find, { user, user.id}),
        function (friends, cb) {
          if (user.websiteAccount !== 'None') {
            post(friend, user, function (err, data) {
              if (err) {
                cb(err);
              } else {
                sendMail(friend, user, cb);
              }
            });
          } else {
            sendMail(friend, user, cb);
          }
        }
      ], cb);
    });
  }
], function (err) {
  if (err) {
    // all the errors in one spot
    throw err;
  }
  console.log('all done');
});

Also, this is you doing a join, SQL is really good at those.

like image 31
generalhenry Avatar answered Nov 10 '22 09:11

generalhenry