使用promise时如何退出串行循环?

问题描述 投票:1回答:1

我有一个长文本文件,我逐行循环以提取一些事件数据并将其存储在数据库中。该文件定期更新,顶部显示新数据。发生这种情况时,我会再次运行文件以提取新事件,但是当我遇到数据库中已有的事件时,我想停止操作(文件总是按最新到旧的顺序排序)。

使用reduce()中描述的this answer方法解决问题Correct way to write loops for promise,我想出了此功能来分析文件:

function parse(
    file)
{
    var lines = file.split("\n"),
        latestDate;

    return lines.reduce(function(promise, line) {
        return promise.then(function() {
            if (/* line matches date pattern */) {
                latestDate = line;
            } else if (/* line matches event pattern */) {
                return Event.createAsync(line, latestDate);
            }

            return promise;
        });
    }, Promise.resolve())
        .catch({ errorName: "uniqueViolated" }, 
            function() { /* ignore only the createAsync error */ });
}

createAsync()数据库方法返回一个在保存事件时已解决的承诺。如果该事件已经存在于数据库中,它将引发异常,这将终止Promise链,因此不会解析文件的其余部分。该异常在函数末尾被catch()处理程序捕获并忽略。我正在Node.js中使用Bluebird 3.0 Promise库。

此函数确实按顺序循环遍历每行,并在遇到已保存的事件时正确停止。但是我想知道这是否是在处理承诺时突破循环的最佳方法。在函数末尾吞下引发的异常似乎有点混乱。

欢迎提出任何改进循环处理的建议。

解决方案?

基于jib's answer,并考虑到Bergi's comment,也许我应该尝试对我链接到的问题进行非简化的回答:),我想出了以下解决方案:

function parse(
    file)
{
    var lines = file.split("\n"),
        latestDate;

    return promiseEach(lines, function(line) {
        if (/* line matches date pattern */) {
            latestDate = line;
        } else if (/* line matches event pattern */) {
            return Event.createAsync(line, latestDate)
                .catch({ errorType: "uniqueViolated" }, function() { return false; });
        }
    });
}

循环递归被移入通用函数promiseEach()中,该函数遍历数组中的每个项目。如果迭代器函数返回了一个Promise,则在该Promise解析之前,不会处理下一项。如果迭代器返回false,则循环结束,点划线样式:

function promiseEach(
    list,
    iterator,
    index)
{
    index = index || 0;

    if (list && index < list.length) {
        return Promise.resolve(iterator(list[index])).then(function(result) {
            if (result !== false) {
                return promiseEach(list, iterator, ++index);
            }
        });
    } else {
        return Promise.resolve();
    }
}

我想这就是我想要的,但是我想知道如果在4000行文件上运行它是否会出现调用堆栈问题。

javascript node.js promise bluebird
1个回答
4
投票

您拥有的东西实际上根本不会跳出循环。

每次对Event.createAsync的调用都会立即成功返回一个Promise,这意味着您总是减少entire数组。

因此,此循环产生的承诺链的长度将始终是文件中的总行数,减去特定逻辑中既不适合日期也不适合事件模式的行数。

这是此promise链的异步执行,由于数据库中已存在事件而在引发错误时,该请求链随后会终止。

您的代码有效,但是您说这是一个长文本文件,所以它可能效率不高,特别是如果尽早突破是规范而不是例外(听起来像是您的描述)。

因此,我将考虑使用递归方法:

function parse(file) {
  var latestDate;

  function recurse(lines, i) {
    if (i >= lines.length) return Promise.resolve();

    var line = lines[i];
    if (/* line matches date pattern */) {
      latestDate = line;
    } else if (/* line matches event pattern */) {
      return Event.createAsync(line, latestDate).then(() => recurse(lines, i + 1));
    }
    return recurse(lines, i + 1);
  }

  return recurse(file.split("\n"), 0);
}

递归方法的好处是,当Event.createAsync解析时,仅在需要时才异步扩展承诺链。您也可以仅停止调用recurse来停止,即无需Event.createAsync抛出异常即可爆发。

一种可视化差异的方法

可能是将其与铺设火车的轨道进行比较,其中轨道代表承诺链,而火车则表示所承诺的异步操作的执行:

使用reduce,您总是总是在火车开始前先放下整个轨道,而不管火车在异常停止之前最终沿着轨道走下多远。您每次都要花费铺设整个轨道的成本(虽然可能不多,但可以累加)。

recurse示例中,您像Gromit in the finale of "The Wrong Trousers"一样在移动火车的前面即时铺设了下一条轨道,因此不会浪费时间来铺设不需要的轨道。

© www.soinside.com 2019 - 2024. All rights reserved.