我正在与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%完美..
(对不起,如果这是一个疯狂的问题,但这种类型的编程很奇怪,我觉得我正在设计反对它而不是它,所以我正在寻找见解)
答案 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);
});
}