异步JavaScript解决方案

Asynchronous JavaScript workarounds

本文关键字:解决方案 JavaScript 异步      更新时间:2023-09-26

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

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

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

这需要调用Mongoose的模型。找出哪个是异步调用。回调将填充一个对象,其中包含所有稍后可以在战斗命令处理器中使用的能力信息。(这是麻烦的地方)。

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

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

想象现在你在这个战斗处理器方法中。你有一个模型。Find来获取必须处理的敌人集合,在Find的回调中,你必须执行一个模型。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是异步的,在第一个findOne回调执行时,enemyList将被完全填充,但尽管如此,它不需要被完全填充以有效执行(即回调中使用的目标对象将在执行findOne时可用)。一旦findOne被执行,它就会遍历敌人列表以找到自己尚未被处理的playerID——记住多个敌人可以针对同一个玩家,所以除了简单地查找playerID外,还需要一个处理过的变量。

有更好的方法吗?

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

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

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

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