【问题标题】:Node.js: How to run asynchronous code sequentiallyNode.js:如何顺序运行异步代码
【发布时间】:2014-12-31 17:27:56
【问题描述】:

我有这段代码

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);
    }
});

这里发生了一些异步的事情。对于每个用户,我想找到他们的相关朋友并将他们连接到一个变量。然后我想检查该用户是否有网站帐户,如果有,请发出帖子请求并在那里获取一些信息。唯一的问题是,由于代码没有等待回调完成,所以一切都乱七八糟地发生了。我一直在使用睡眠,但这也不能解决问题,因为它仍然很混乱。

我研究过 async,但这些函数是相互交织的,并不是真正独立的,所以我也不确定它如何与 async 一起工作。

有什么建议可以让这段代码按顺序运行吗?

谢谢!

【问题讨论】:

  • 这里有一个类似的问题可能会有所帮助:stackoverflow.com/questions/9644197/…
  • 回调的重点是代码不会等待。如果代码等待,则不需要回调。虽然您可以使用“Promise”解决方案,但最好先了解基本问题以及如何在没有抽象的情况下管理它。然后根据需要使用抽象。

标签: javascript node.js asynchronous


【解决方案1】:

我更喜欢 promise 模块而不是 q https://www.npmjs.com/package/promise,因为它很简单

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
});

如您所见,您可以永远这样继续下去。您还可以从异步处理程序返回简单值而不是承诺,然后这些值将简单地传递给 then 回调。

【讨论】:

    【解决方案2】:

    我重写了您的代码,使其更易于阅读。如果您想保证同步执行,您有几种选择:

    1. 使用async 库。它提供了一些帮助函数来串行运行您的代码,特别是:https://github.com/caolan/async#seriestasks-callback

    2. 使用 Promise 避免进行回调,并简化您的代码 API。 Promise 是 Javascript 中的一项新功能,尽管在我看来,您现在可能不想这样做。对 Promise 的库支持仍然很差,并且无法将它们与许多流行的库一起使用:(

    现在——关于你的程序——你的代码实际上没有任何问题(假设你在伪代码块中没有异步代码)。您的代码现在可以正常工作,并且会按预期执行。

    我建议您目前使用 async 来满足您的顺序需求,因为它同时适用于服务器端和客户端,基本上可以保证与所有流行的库一起使用,并且经过良好的使用/测试。

    清理下面的代码

    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);
      }
    });
    

    【讨论】:

      【解决方案3】:

      首先让我们更实用一些

      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);
        });
      });
      

      然后让我们异步

      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');
      });
      

      另外,这是你在做一个连接,SQL 真的很擅长这些。

      【讨论】:

        【解决方案4】:

        你会想要研究一个叫做 Promise 的东西。它们将允许您链接事件并按顺序运行它们。这是一个关于它们是什么以及如何使用它们的很好的教程http://strongloop.com/strongblog/promises-in-node-js-with-q-an-alternative-to-callbacks/

        【讨论】:

          【解决方案5】:

          您还可以查看 Async JavaScript 库:Async 它提供了用于在 JavaScript 中排序异步函数执行的实用函数。

          【讨论】:

            【解决方案6】:

            注意:我认为您在处理程序中执行的查询次数是代码异味。在查询级别可能会更好地解决此问题。话虽如此,让我们继续吧!

            很难确切地知道你想要什么,因为你的伪代码可以使用清理恕我直言,但我要你想做的是:

            1. 获取所有用户,并为每个用户 一种。获取所有用户的朋友和每个朋友:
              • 如果用户有网站帐户,则发送帖子请求
              • 发送电子邮件
            2. 在进程完成后做一些事情

            您可以通过多种不同的方式做到这一点。普通回调或异步工作得很好;我将提倡promises,因为它们是未来,而且图书馆支持非常好。我将使用rsvp,因为它很轻,但任何符合 Promise/A+ 的库都可以解决问题。

            // helpers to simulate async calls
            var User = {}, Friend = {}, request = {};
            var asyncTask = User.find = Friend.find = request.post = function (cb) {
              setTimeout(function () {
                var result = [1, 2, 3];
                cb(null, result);
              }, 10);
            };
            
            User.find(function (err, usersResults) {
              // we reduce over the results, creating a "chain" of promises
              // that we can .then off of
              var userTask = usersResults.reduce(function (outerChain, outerResult) {
                return outerChain.then(function (outerValue) {
                  // since we do not care about the return value or order
                  // of the asynchronous calls here, we just nest them
                  // and resolve our promise when they are done
                  return new RSVP.Promise(function (resolveFriend, reject){
                    Friend.find(function (err, friendResults) {
                      friendResults.forEach(function (result) {
                        request.post(function(err, finalResult) {
                          resolveFriend(outerValue + '\n finished user' +  outerResult);
                        }, true);
                      });
                    });
                  });
                });
              }, RSVP.Promise.resolve(''));
            
              // handle success
              userTask.then(function (res) {
                document.body.textContent = res;
              });
            
              // handle errors
              userTask.catch(function (err) {
                console.log(error);
              });
            });
            

            jsbin

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-10-08
              • 1970-01-01
              • 2018-06-08
              • 2015-12-17
              • 2021-05-02
              • 1970-01-01
              • 2011-08-19
              • 1970-01-01
              相关资源
              最近更新 更多