Node.js:如何按顺序运行异步代码

5
我有这段代码。
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);
    }
});

这里有几个异步操作。对于每个用户,我想找到他们相关的好友,然后将它们连接到一个变量中。然后,我想检查该用户是否拥有网站帐户,如果有,就进行一次POST请求并获取一些信息。唯一的问题是,由于代码没有等待回调完成,所以所有操作都是无序的。我一直在使用sleep,但这也不能解决问题,因为依然会混乱。
我尝试过使用async,但这些函数是交织在一起的,不太容易分开,所以我不确定如何使用async。
有什么建议可以让这段代码按顺序运行吗?
谢谢!

这里有一个类似的问题可能会有所帮助:https://dev59.com/OmHVa4cB1Zd3GeqPmGC- - tier1
1
回调函数的目的是让代码不必等待。如果代码需要等待,那么就没有必要使用回调函数了。虽然你可以使用“Promise”解决方案,但最好先了解基本问题以及如何在没有抽象的情况下管理它。然后再根据需要使用抽象。 - user1106925
6个回答

6

我更喜欢 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);
  }
});

2

首先,让我们更加注重功能性。

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在这方面非常擅长。

1

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

很难确定你想要什么,因为你的伪代码需要清理,但我会告诉你想要做的事情是这样的:

  1. 获取所有用户,并对于每个用户 a. 获取所有用户的朋友,并对于每个朋友:
    • 如果用户有网站账户,则发送一个POST请求
    • 发送一封电子邮件
  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

可以翻译为:

{{链接1:jsbin}}


1

1
你还可以查看Async JavaScript库:Async。它为JavaScript中异步函数的执行提供了实用程序函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接