【问题标题】:Asynchronous JavaScript workarounds异步 JavaScript 解决方法
【发布时间】:2015-10-22 00:44:03
【问题描述】:

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

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

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

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

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

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

想象一下,您现在处于这种战斗处理器方法中。您有一个 model.find 来获取必须处理的一组敌人,并且在该 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 被执行,它会遍历敌人列表以找到它自己的当前尚未处理的 playerID - 请记住,多个敌人可以针对同一个玩家,因此除了需要一个已处理的变量之外只需查找 playerID。

有没有更好的办法?

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

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

【问题讨论】:

  • 啊废话哈哈,太早了。每次我说同步时,我的意思是异步。
  • 不用担心。 :) 您几乎有两个选择,使用承诺,或带有回调的计数器,您更喜欢哪一个?我倾向于更倾向于 Promise,因为使用 Promise 处理错误通常更容易。
  • 好吧,把这些词都翻了。希望它现在更有意义
  • @KevinB promises/callbacks 是很好的解决方案,但我主要关心的是示例 2,其中数据基本上需要传入并由回调使用

标签: javascript node.js multithreading callback mongoose


【解决方案1】:

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

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

【讨论】:

  • 哇,这太酷了。我没有考虑使用这样的承诺!我会试一试。事实上,我认为这正是我最初想要的——基本上是回调函数本身的回调。 Promise.all 甚至涵盖了处理多个迭代回调,sweeeeeet。
  • 我的回答可能有点过于复杂,但是,如果您能找到一种方法,通过一个查询获得所有需要的玩家,同时仍然能够将他们与每个敌人相关联,那么它可能会更有效。例如,使用$in: ['playerid','playerid']
  • 是的——在这种情况下,可能会执行更好的查询,但我确信在某些情况下这种方法不可行,并且需要 Promise.all 来支持它。你能推荐一个与 Node 配合得很好的 Promise 库吗?我在 Q 上看到了好东西
  • 我使用 --harmony 标志来获得原生承诺,但 blurbird 和 Q 都是不错的选择。我通常使用蓝鸟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-14
  • 2013-12-03
  • 2011-09-03
相关资源
最近更新 更多