打破 Bluebird .each() 进程

Break a Bluebird .each() process

我正在从 Async 转换到 Bluebird,但不知道如何打破循环

这是我想要实现的目标:

  1. 遍历数据数组。
  2. 对于每个项目,检查它是否存在于数据库中。
  3. 一个项添加到数据库(第一个不存在的项),然后退出.each()循环。

任何帮助将不胜感激。

Bluebird 没有用于此类操作的内置函数,它有点难以适应 promise 迭代模型,因为迭代器 return 不是真正的单个值(promise)让您有机会回复 success/error 并停止迭代。

使用拒绝停止迭代

您可以使用 Promise.each(),但您必须使用编码拒绝才能像这样停止迭代:

var data = [...];

Promise.each(data, function(item, index, length) {
    return checkIfItemExists(item).then(function(exists) {
        if (!exists) {
            return addItemToDb(item).then(function() {
                // successfully added item to DB
                // lets reject now to stop the iteration
                // but reject with a custom signature that can be discerned from an actual error
                throw {code: "success", index: index};
            });
        }
    })
}).then(function() {
    // finished the iteration, but nothing was added to the DB
}, function(err) {
    if (typeof err === "object" && err.code === "success") {
        // success
    } else {
        // some sort of error here
    }
});

如果您必须经常使用此结构,可以将其放入可重复使用的 function/method 中。您只需要为被拒绝的承诺采用一个约定,该约定实际上只是为了成功停止迭代而不是实际错误。

这看起来确实是一个有趣且并非所有不常见的需求,但我还没有看到任何特定定义的结构与 Promises 来处理此类问题。


如果感觉像在上面的场景中重载拒绝是太多的 hack(它确实如此),那么您可以编写自己的迭代方法,该方法使用已解析的值约定来告诉迭代器什么时候停止:

自定义迭代

Promise.eachStop = function(array, fn) {
    var index = 0;

    return new Promise(function(resolve, reject) {

        function next() {
            if (index < array.length) {
                // chain next promise
                fn(array[index], index, array.length).then(function(result) {
                    if (typeof result === "object" && result.stopIteration === true) {
                        // stopped after processing index item
                        resolve(index);
                    } else {
                        // do next iteration
                        ++index;
                        next();
                    }
                }, reject);
            } else {
                // finished iteration without stopping
                resolve(null);
            }
        }

        // start the iteration
        next();
    });
}

此处如果迭代器解析的值是一个对象具有 属性 stopIteration: true,则迭代器将停止。

如果任何地方出现错误,最终的承诺将拒绝,如果迭代器完成且从未停止,则最终承诺将以 null 的值解决,或者使用作为迭代停止处的索引的数字解决。

你可以这样使用它:

Promise.eachStop(data, function(item, index, length) {
    return checkIfItemExists(item).then(function(exists) {
        if (!exists) {
            return addItemToDb(item).then(function() {
                // return special coded object that has stopIteration: true
                // to tell the iteration engine to stop
                return {stopIteration: true};
            });
        }
    })
}).then(function(result) {
    if (result === null) {
        // finished the iteration, but nothing was added to the DB
    } else {
        // added result item to the database and then stopped further processing
    }
}, function(err) {
    // error
});

告诉迭代器是否跳过其工作的标志变量

在进一步思考这个问题时,我想出了另一种方法,允许 Promise.each() 迭代到 运行 完成,但设置一个更高范围的变量来告诉你的迭代器什么时候它应该跳过它的工作:

var data = [...];
// set property to indicate whether we're done or not
data.done = false;

Promise.each(data, function(item, index, length) {
    if (!data.done) {
        return checkIfItemExists(item).then(function(exists) {
            if (!exists) {
                return addItemToDb(item).then(function() {
                    data.done = true;
                });
            }
        })
    }
}).then(function() {
    // finished
}, function(err) {
    // error
});