迭代promises节点js

时间:2017-10-04 13:19:43

标签: javascript node.js promise

嗨我有一个问题,我正在尝试迭代一个键列表,创建一个匹配实体列表 代码看起来像这样

let previous = undefined;
for (let i = offset; i < response.keys.length; i++) {
    logger.debug("iteration " + i + " previous: " + previous)
    logger.debug("instance key: " + response.keys[i])
    if (previous) {
        previous = previous
            .then((entities) => {
                if (entities.length === limit) {
                    i = response.keys.length;
                } else {
                    readEntity(response.keys[i])
                        .then((instance) => {
                            matchInstance(instance, conditions)
                            logger.debug("is match: " + isMatch)
                                .then((isMatch) => {
                                    if (isMatch)
                                        entities.push(instance);
                                    //resolve(entities);
                                }).catch((err) => {
                                reject(err)
                            })
                        }).catch((err) => {
                        reject(err)
                    })
                }
            }).catch((err) => {
                reject(err)
            })
    } else {
        previous = readEntity(response.keys[i])
            .then((instance) => {
                logger.debug("reading instance: " + instance.key)
                matchInstance(instance, conditions)
                    .then((isMatch) => {
                        if (isMatch) {
                            return instance
                        } else {
                            logger.debug("instance does not match")
                        }
                    }).catch((err) => {
                    reject(err)
                })
            }).catch((err) => {
                reject(err)
            })
    }
}

但它只通过for循环一次,就像,它没有返回任何东西?

我也有一些调试

2017-10-04T15:09:58+0200 <debug> data.js:202 (seekKeys.then) iteration 0 previous: undefined
2017-10-04T15:09:58+0200 <debug> data.js:203 (seekKeys.then) instance key: employees/existing@...
2017-10-04T15:09:58+0200 <debug> data.js:202 (seekKeys.then) iteration 1 previous: [object Promise]
2017-10-04T15:09:58+0200 <debug> data.js:203 (seekKeys.then) instance key: employees/test@...
2017-10-04T15:09:58+0200 <debug> data.js:202 (seekKeys.then) iteration 2 previous: [object Promise]
2017-10-04T15:09:58+0200 <debug> data.js:203 (seekKeys.then) instance key: employees/unique@...
2017-10-04T15:09:58+0200 <debug> data.js:202 (seekKeys.then) iteration 3 previous: [object Promise]
2017-10-04T15:09:58+0200 <debug> data.js:203 (seekKeys.then) instance key: employees/update@...
2017-10-04T15:09:59+0200 <debug> data.js:231 (readEntity.then) reading instance: existing@...
2017-10-04T15:09:59+0200 <debug> data.js:237 (matchInstance.then) instance does not match

如果您需要更多信息,请告诉我。谢谢

2 个答案:

答案 0 :(得分:1)

下一次,如果不是只是在一个问题中转储一堆代码,那么你会更容易提供帮助,你实际上已经解释了你想要用语言完成的内容(以及代码)。我试图对代码的目标进行逆向工程,这就是我认为你想要做的事情:

  1. 您在response.keys
  2. 中有一系列值
  3. 您希望累积这些密钥的第一个limit个匹配项,并在limit个匹配后停止查找更多匹配项。
  4. 您希望按response.keys中的项目顺序查找这些匹配项。
  5. 使用readEntity(val).then()检测匹配,然后进行matchInstance().then()测试。
  6. 所需的结果是匹配的条目数组不长于limit
  7. 当进程中的任何位置发生错误时,您希望中​​止并报告错误。
  8. 以下是三个单独的设计模式,用于序列化数组的迭代,您在每个数组元素上执行异步操作,并希望一个接一个地(不是并行)执行它们。在收集limit结果后,要求停止处理,使您的特定情况稍微复杂一些。

    .reduce()承诺链接

    用于序列化基于承诺的异步操作的经典设计模式是使用.reduce(),其中减少中的累加器值是一个承诺,并链接到该承诺以强制序列化。您可以这样实现:

    // get subset of response.keys that we can process with .reduce()
    let keys = response.keys.slice(offset);
    keys.reduce((p, val) => {
        // return promise so we continually chain
        return p.then(entities => {
            // if entities is not yet full
            if (entities.length < limit) {
                return readEntity(val).then(instance => {
                    return matchInstance(instance, conditions).then(isMatch => {
                        if (isMatch) {
                            entities.push(instance);
                        }
                        // resolved value is entities so that is passed down the chain
                        return entities;
                    });
                });
            } else {
                return entities;
            }
        });
    }, Promise.resolve([])).then(entities => {
        // process results here
    }).catch(err => {
        // process error here
    });
    

    示例实施:https://jsfiddle.net/jfriend00/uspa8vgd/

    在此承诺链中,promise的已解析值是结果数组。它被初始化为一个空数组,其中Promise.resolve([])作为初始累加器值传递给.reduce(),然后.reduce()回调的结算值总是相同entities阵列。通过这种方式,它会在链中传递,链中的每个链接都有机会添加(或不基于isMatch测试)。

    手动迭代并使用next()函数链接

    这是另一种按顺序迭代的设计模式。我创建了一个通常称为next()的内部函数。该函数要么返回一个promise或一个值。如果它返回一个值,则迭代完成。如果它返回一个promise,则迭代继续,每次迭代链接到前一个迭代。最终解析的值是您在迭代期间累积的任何值。在这里它是entities数组。

    function runMatchIteration(data, offset, limit) {
        let i = offset;
        let entities = [];
    
        function next() {
            if (i < data.length && entities.length < limit) {
                return readEntity(data[i++]).then(instance => {
                    return matchInstance(instance, conditions).then(isMatch => {
                        if (isMatch) {
                            entities.push(instance);
                        }
                        // now execute next cycle - chaining onto original promise
                        return next();
                    });
                });
    
            } else {
                // done with loop here
                return entities;
            }
        }
    
        return Promise.resolve().then(next);
    }
    
    
    // usage
    runMatchIteration(response.keys, offset, limit).then(entities => {
        // process results here
    }).catch(err => {
        // process error here
    });
    

    示例实施:https://jsfiddle.net/jfriend00/t5bmzkb6/

    使用Bluebird的Promise.mapSeries()在系列中运行

    Bluebird promise library有许多有用的功能来管理和排序承诺。其中包括Promise.mapSeries(),它将数组作为输入并序列化调用数组中每个项的函数。因为你要求在limit结果之后停止,我们必须使用它与平时略有不同,但它仍然使代码相当简单:

    let entities = [];
    Promise.mapSeries(response.keys.slice(offset), item => {
        if (entities.length < limit) {
            return readEntity(item).then(instance => {
                return matchInstance(instance, conditions).then(isMatch => {
                    if (isMatch) {
                        entities.push(instance);
                    }
                });
            });
        }
    }).then(() => {
        // process entities result here
    }).catch(err => {
        // handle error here
    });
    

    对原始代码的一些观察:

    1. 您正在使用Promise constructor anti-pattern在手动创建的承诺中包装其他承诺,而不是仅仅返回您已有的承诺。
    2. .then()处理程序中运行新的异步操作时,必须从.then()回调中返回这些promise,以便它们链接到父承诺。否则,您将创建各种独立的未链接的承诺链,并且您永远不知道何时完成任何事情并且无法协调不同的承诺链。
    3. 您的代码将大致相同的逻辑复制到两个地方,这绝不是一个好主意。
    4. 您无法在异步操作中操纵for循环索引。 for循环是同步的,并且在任何异步回调执行之前已经完成了很长时间。因此,每当您尝试操作其索引时,for循环就已完成。

答案 1 :(得分:0)

您可以为此(不推荐)

编写递归函数
let index = 0;
let errors = [];
function promiseIterator (index) {
    if (response.keys[index]) {
      readEntity(response.keys[index]).then((instance) => {
         matchInstance(instance, conditions).then(() => {
           logger.debug("is match: " + isMatch).then(() => {
             if (isMatch) entities.push(instance);
             promiseIterator(index+1);
           });
         });
      }).catch((err) => {
        //error handler
        errors.push({keyIndex: index, err: err});
        promiseIterator(index+1);
      });
    }
}

promiseIterator(offset);
// do your stuff with error array

上述代码可能不准确,请根据您的要求进行调整。

但是,由于您需要对承诺进行迭代,因此建议使用bluebird库并实施eachall功能取决于您的要求。