Bluebird.each 如果解决了就中断

Bluebird.each break if solved

我想测试数组的每个元素,直到满足条件,然后跳过其余部分。这是我想出的代码,它似乎可以工作,但我不确定它是否真的安全或有意想不到的副作用。欢迎其他解决方案。

let buddyAdded = false;
replicaArr = _.keys(ReplicaList);
Promise.each(replicaArr, function(replicaname) {
  if (!buddyAdded) {
    Player.count({
      'buddyList': replicaname
    }, function(err, result) {
      if (err) {

      } else if (result < 990) {

        Player.update({
          '_id': buddyObj.buddyId
        }, {
          'buddyList': replicaname
        }, function(err) {
          if (err) {

          } else {
            ReplicaList[replicaname].send(buddyObj);
            buddyAdded = true;
            // success stop here and skip all the other  array elements
            return;
          }
        });
      }
    });
  }
});

如果您试图一次一个地连续枚举玩家,并在发现玩家的好友列表中有空间时中止迭代,您可以更新列表并反馈发生的任何错误,那么这里是一种方法。

工作原理如下:

  1. 使用 Bluebird 的 Promise.promisifyAll() 自动为 Player 对象创建 returning 方法,这样我们就可以在我们的控制流中使用它们。
  2. 使用 Bluebird 的 Promise.mapSeries() 一次一个地连续迭代数组。
  3. 链接 Player.countAsync()Player.updateAsync() 方法,使它们正确排序,return 它们来自 .mapSeries(),因此在继续迭代到下一个之前等待它们完成数组元素。
  4. 如果我们找到有房间的玩家并成功更新好友列表,则抛出一个特殊的异常。这将拒绝当前的承诺链并导致 .mapSeries() 停止它的迭代(这就是你所说的你想要的)。
  5. 在更高级别添加一个 .catch() 来测试特殊拒绝并将其转化为成功解决的承诺。如果是其他错误,则让它作为实际错误继续传播。

代码:

// Promisify the Player object so the methods
// this would usually be done wherever this module is loaded
Player = Promise.promisifyAll(Player);

// create a special subclass of Error for our short circuit
PlayerUpdateDone extends Error {
    constructor(name) {
        super();
        this.name = name;
    }
}

// put logic into a function to make it cleaner to use
function addToBuddyList(replicaArr) {

    return Promise.mapSeries(replicaArr, function(replicaname) {
        return Player.countAsync({buddyList: replicaname}).then(function(result) {
            // if room left in buddy list
            if (result < 990) {
                return Player.updateAsync({'_id': buddyObj.buddyId}, {'buddyList': replicaname}).then(function() {
                    ReplicaList[replicaname].send(buddyObj);
                    // throw special exception to abort .mapSeries()
                    //    and stop further processing
                    throw new PlayerUpdateDone(replicaname);
                });
            }
        });
    }).then(function() {
        // if it gets here, there were no players with rooms so just return null
        return null;
    }).catch(function(result) {
        // we used a special rejection as a shortcut to stop the mapSeries from executing
        //    the rest of the series
        if (result instanceof PlayerUpdateDone) {
            // change this special rejection into a result
            return result.name;
        }
        // must have been regular error so let that propagate
        throw result;
    });
}

// sample usage
addToBuddyList(replicaArr).then(function(name) {
    if (name) {
        console.log(`Updated player ${name}`);
    } else {
        console.log("No players with room in their buddy list");
    }
}).catch(function(err) {
    // error here
    console.log(err);
});

制作自己的排序器在第一个 promise 解析为真值时停止可能更简单:

// fn will be passed each array element in sequence
// fn should return a promise that when resolved with a truthy value will stop the iteration
//    and that value will be the resolved value of the promise that is returned from this function
// Any rejection will stop the iteration with a rejection
Promise.firstToPassInSequence = function(arr, fn) {
    let index = 0;

    function next() {
        if (index < arr.length) {
            return Promise.resolve().then(function() {
                // if fn() throws, this will turn into a rejection
                // if fn does not return a promise, it is wrapped into a promise
                return Promise.resolve(fn(arr[index++])).then(function(val) {
                    return val ? val : next();
                });
            });
        }
        // make sure we always return a promise, even if array is empty
        return Promise.resolve(null);
    }
    return next();
};


Promise.firstToPassInSequence(replicaArr, function(replicaname) {
    return Player.countAsync({buddyList: replicaname}).then(function(result) {
        // if room left in buddy list
        if (result < 990) {
            return Player.updateAsync({'_id': buddyObj.buddyId}, {'buddyList': replicaname}).then(function() {
                ReplicaList[replicaname].send(buddyObj);
                return replicaname;
            });
        }
    });
});