异步 JavaScript 解决方法

Asynchronous JavaScript workarounds

我正在使用 MEANjs 进行一些工作以帮助获得一些使用经验,但我遇到了 Mongoose 的异步函数调用问题。我不得不建立变通办法,因为我一心想以同步的心态编码。我做错了吗?

示例 1:在异步回调结束时调用下一个操作

例如,想象一个具有服务器端战斗代码的 JavaScript 游戏 (MEANjs)。此代码将获取任何具有战斗命令(例如攻击、射击火球等)的 AI 敌方实体并处理这些命令。然而,需要做的第一件事是从 MongoDB 中查找可用能力列表,以便我们知道这些战斗命令需要做什么。例如,如果一个敌人想要执行他们的 "Spit Fire IV" 能力,返回的信息会让我们知道这会造成多少伤害等。

这需要调用 Mongoose 的 model.find,这是一个异步调用。回调将使用所有能力信息填充一个对象,这些信息稍后可以在战斗命令处理器中使用。 (这是麻烦的部分)。

一旦 model.find 的回调被调用,数据将被存储,并且 processCombat() 函数将在此回调结束时执行。这里没有问题,对吧,这是最佳实践吗?我过去只是在调用这种类型的异步调用后放置一个 setTimeout 以确保填充对象,但这似乎是一个糟糕的设计。

示例 2:将数据传递给迭代调用的异步方法

想象一下,现在您正处于这种战斗处理器方法中。您有一个 model.find 来获取必须处理的一组敌人,并且在该查找的回调中,您必须执行一个 model.findOne 来获取该敌人的目标玩家并在其回调中执行某些操作,例如验证战斗命令,改变玩家的生命值,更新敌人让它知道它已经处理了它的战斗命令,等等

伪代码:对于每个准备好进行战斗的敌人,1)获取敌人,2)获取战斗中瞄准的玩家,3)更新玩家和敌人数据以完成战斗,即玩家输了一些健康。

Enemy.find(... function(err,enemies) {
    var enemyList = [];
    for(var i=0; i < enemies.Length; i++) {
        enemyList.push({
          playerID: enemies[i].combatTargetID,
          enemy: enemies[i],
          processed: false
        });

        Player.findOne({_id: enemies[i].combatTargtID}, function(err,player) {
            var enemy = null;
            for(var j = 0; j < enemyList.length; j++) {
                if(player.id === enemyList[j].playerID && enemyList[j].processed === false) {
                    enemy = enemyList[j].enemy;
                    enemyList[j].processed = true;
                    break;
                }
            }
            //do things with enemy and player!
        });
    }
});

重要的部分是填充 enemyList,然后在异步回调中使用它。很有可能,因为 findOne 是异步的,所以 enemyList 将在第一个 findOne 回调执行时被完全填充,但尽管如此,它不需要被完全填充就可以有效地执行(即在中使用的目标对象)回调将在执行 findOne 时可用)。一旦 findOne 被执行,它会遍历敌人列表以找到它自己当前尚未处理的 playerID——请记住,多个敌人可以瞄准同一个玩家,因此除了只需查找 playerID。

有没有更好的方法?

对此的警告:如果同时调用多个回调怎么办。可以在同一行上创建多个实例的竞争条件,因此使用处理后的变量不会 100% 完美..

(很抱歉,如果这是一个疯狂的问题,但这种类型的编程很奇怪,我觉得我是在针对它而不是使用它进行设计,所以我正在寻找见解)

在这种情况下,我会将流程分解为 return 承诺的几个函数,然后将它们链接在一起。

function getEnemies (obj) {
  return new Promise(function (resolve) {
    Enemy.find(... function(err,enemies) {
      if (err) {throw err;}
      obj.enemies = enemies.map(function (enemy) {
        return {
          playerID: enemy.combatTargetID,
          enemy: enemy,
          processed: false
        };
      });
      resolve(obj);
    });
  });
}

function getPlayers (obj) {
  return Promise.all(obj.enemies.map(function (enemy) {
    return new Promise(function (resolve) {
      Player.findOne({_id: enemy.combatTargtID}, function(err,player) {
        if (err) {throw err;}
        enemy.player = player;
        resolve();
      });
    })
  }).then(function () {
    return obj;
  });
}

function doWork() {
  getEnemies()
    .then(getPlayers)
    .then(function (obj) {
      console.log(obj); // do stuff with enemies and players here
    }).catch(function (err) {
      console.log(err, err.stack); 
    });
}