Javascript forEach 非阻塞查询

Javascript forEach non blocking query

在我的控制器 (sails.js) 中,我有一个包含 ID 列表的数组。

这些是另一个 table 的 ID。我需要使用该 ID 提取每条记录,并将其发送给用户。

到目前为止我已经制作了这段代码:

     suggestions.forEach(function (element, index, array){
                    Suggester.findOne({
                    "id": element.suggester_id
                    },function(err,docs){
                        suggesterResults.push(docs);
                        console.log("I am adding to array: " + docs);
                        if (index === array.length - 1) {
                            completeSend(suggesterResults);
                        }
                    });
                })
    ...
    function completeSend (results) {
        console.log("I am in complete send method"  + results)
        return res.send(results, 200);
    }

行得通,但看起来像是作弊。在我看来,这是阻塞代码,而不是 acceptable。在这种情况下有没有通常的处理方式?

如果 findOne 异步完成,而且看起来确实如此,那么 forEach 可能会阻塞几分之一毫秒。 以后findOne 将在每个查找完成时调用每个回调。从阻塞的角度来看,该代码很好,前提是 findOne 异步完成。

但是,代码有一个不同的问题:你假设回调将按顺序发生,方法是:

if (index === array.length - 1) {
    completeSend(suggesterResults);
}

除非 findOne 记录它,否则您不能做出该假设(我查看了 sails.js 网站;除了没有说什么的项目符号列表)。回调可能会乱序到达,例如,如果一次查找比之前的查找快。

相反,您需要跟踪您收到了多少 次回叫,并在收到与请求相同的电话号码时调用completeSend已经做了,而不是依赖索引。

如果开始时 suggesterResults 是空白的,并且在调用未完成时没有任何内容可以修改 suggestions,您可以使用它的 length:

suggestions.forEach(function (element, index, array){
    Suggester.findOne({
    "id": element.suggester_id
    },function(err,docs){
        suggesterResults.push(docs);
        console.log("I am adding to array: " + docs);
        if (suggesterResults.length === array.length) {
            completeSend(suggesterResults);
        }
    });
})

但是,如果这些警告中的任何一个都不成立,您最好使用计数器:

var waitingon = 0;
suggestions.forEach(function (element, index, array){
    ++waitingon;
    Suggester.findOne({
    "id": element.suggester_id
    },function(err,docs){
        suggesterResults.push(docs);
        console.log("I am adding to array: " + docs);
        if (--waitingon === 0) {
            completeSend(suggesterResults);
        }
    });
})

看起来 像竞争条件,但这不是因为这是一个单线程环境。所有 forEach 回调都将在第一个 findOne 回调发生之前发生,因此 waitingon 将在我们开始递减之前上升到适当的水平。 (这也考虑到 suggestions 稀疏的可能性,这似乎不太可能。)

承诺:

Promise.all(suggestions.map(function(suggestion) {
    return Suggester.findOne({"id": suggestion.suggester_id});
})
.then(completeSend);

让我们解释每一行:

<a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#all---promise" rel="nofollow">Promise.all</a>(suggestions.map(function(suggestion) {
    return Suggester.findOne({"id": suggestion.suggester_id});
})

将建议数组映射到新的承诺数组中。 findOne returns 对未来查询结果的承诺。

Promise.all() 是一个静态方法,它接受一个承诺数组,returns 一个单一的承诺,当数组中的所有承诺都成功解析时解析。该承诺 以原始顺序 的所有已解析值的数组解析,这恰好正是您想要的。

.then(completeSend);

这个 .then 被调用的承诺是从 Promise.all() 返回的承诺,所以它等同于用所有 docs 项的数组调用 completeSend(),之后所有的承诺都已兑现。

正确的做法是

Suggester.find({
    id: suggestions.map(function(s) { return s.suggester_id; })
})
.then(completeSend);

您调用 find 一次,并传递一个 ID 数组,完成后,将使用结果数组调用 completeSend


您提到所有这些已经成为承诺链的一部分,在这种情况下 .then() 存在不良做法(不要嵌套 .then() 调用!)

如果是这样,那么正确的方法是:

// Some promise chain logic here
.then(function(/* suggestions? */) {
    return Suggester.find({
        id: suggestions.map(function(s) { return s.suggester_id; })
    });
})
.then(completeSend);