异步JavaScript解决方法

时间:2015-07-30 15:08:35

标签: javascript node.js multithreading callback mongoose

我正在与MEANjs合作以帮助获得一些经验,并且我遇到了Mongoose异步函数调用的问题。我不得不建立解决方案,因为我一直都倾向于以同步思维方式编写代码。我做错了吗?

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

例如,想象一下具有服务器端战斗代码的JavaScript游戏(MEANjs)。此代码将抓取任何具有战斗命令的AI敌人实体(例如攻击,射击火球等)并处理这些命令。但是,首先需要查看MongoDB中可用功能的列表,以便我们知道这些战斗命令需要做什么。例如,如果一个敌人想要执行他们的'#Swit 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是异步的,因此很可能在第一个findOne回调执行时完全填充enemyList,但尽管如此,它还是不需要完全填充它才能有效执行(即目标对象)在执行findOne时可以使用回调中使用的。一旦findOne被执行,它会遍历敌人列表以找到其当前未被处理的玩家ID - 请记住,多个敌人可以瞄准同一玩家,因此需要一个已处理的变量除了简单地查找playerID。

有更好的方法吗?

需要注意的是:如果同时调用多个回调怎么办?可以创建一个竞争条件,其中多个实例在同一行,因此使用已处理的变量将不是100%完美..

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

1 个答案:

答案 0 :(得分:1)

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

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