使用Promise时如何摆脱串行循环? [英] How to break out of a serial loop when using promises?

查看:90
本文介绍了使用Promise时如何摆脱串行循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个长文本文件,我逐行循环以提取一些事件数据并将其存储在数据库中.该文件会定期在顶部使用新数据进行更新.发生这种情况时,我会再次浏览文件以提取新事件,但是当我遇到数据库中已经存在的事件时(我总是将文件从最新到最旧排序),我想停止.

I've got a long text file that I loop through line by line to extract some event data and store it in a database. The file periodically gets updated with new data at the top. When that happens, I run through the file again extracting the new events, but I want to stop when I get to an event that's already in the database (the file is always ordered newest to oldest).

使用此问题的答案中所述的reduce()方法.com/questions/24660096/correct-way-to-write-loops-for-promise>为promise编写循环的正确方法,我想出了一个函数来解析文件:

Using the reduce() approach described in this answer to the question Correct way to write loops for promise, I've come up with this function to parse the file:

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.如果该事件已经存在于数据库中,它将引发异常,这将终止Promise链,因此不会解析文件的其余部分.该异常在函数末尾被catch()处理程序捕获并忽略.我正在Node.js中使用Bluebird 3.0 Promise库.

The createAsync() database method returns a promise that's resolved when the event is saved. It will throw an exception if the event already exists in the database, which stops the promise chain so the rest of the file isn't parsed. That exception is caught and ignored by the catch() handler at the end of the function. I'm using the Bluebird 3.0 promise library in Node.js.

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

This function does loop through each line serially and correctly stops when it hits an already saved event. But I'm wondering if this is the best way to break out of a loop while dealing with promises. Swallowing the thrown exception at the end of the function seems a bit kludgy.

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

Any suggestions for improving the loop handling are welcome.

jib的答案为基础,并考虑到

Building on jib's answer, and taking into account Bergi's comment that maybe I should've just tried his non-reduce answer to the question I linked to :), I came up with this solution:

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,则循环以Lo-dash样式结束:

The loop recursion is moved into a generic function, promiseEach(), that loops over every item in an array. If the iterator function returns a promise, the next item isn't processed until that promise resolves. If the iterator returns false, then the loop ends, Lo-dash style:

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行文件上运行它是否会出现调用堆栈问题.

I think that does what I want, but I'm wondering if there'll be call stack issues if I run it over a 4000-line file.

推荐答案

您拥有的东西实际上并没有完全脱离循环.

What you have doesn't actually break out of a loop at all.

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

Every call to Event.createAsync returns successfully right away with a promise, which means you always reduce over the entire array.

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

The length of the promise-chain produced by this loop will therefore always be the total number of lines in the file, less the number of lines that fit neither the date nor event pattern in your particular logic.

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

It's the asynchronous execution of this promise-chain that's later terminated when an error is thrown because an event already exists in the database.

您的代码有效,但是您说这是一个长文本文件,因此它可能效率不高,尤其是如果尽早突破是规范而不是例外(听起来像是您的描述),则尤其如此.

Your code works, but you said this was a long text file, so it might be inefficient, especially if breaking out early is the norm rather than the exception (which it sounds like from your description).

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

I would therefore consider a recursive approach instead:

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抛出异常来终止.

The benefit of a recursive approach is that the promise chain is extended asynchronously, when Event.createAsync resolves, and only as needed. You can also merely stop calling recurse to stop, i.e. there's no need for Event.createAsync to throw an exception to break out.

可视化差异的一种方法可能是将其与铺设火车的轨道进行比较,其中轨道代表承诺链,而火车则代表异步操作的执行.答应了:

A way to visualize the difference may be to compare it to laying down tracks for a train, where the track represents the promise-chain, and the train represents the execution of the asynchronous operations that are promised:

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

With reduce, you always lay down the entire track first before the train starts, regardless of how far the train ends up going down the track before an exception stops it. You eat the cost of laying down the entire track each time (it may not be much, but it can add up).

recurse示例中,您将在运行的火车前即时放置下一条轨道,例如,因此不会浪费时间来铺设不需要的轨道.

In the recurse example, you're laying down the next piece of track just-in-time in front of the moving train, like Gromit in the finale of "The Wrong Trousers", so no time is wasted laying tracks that wont be needed.

这篇关于使用Promise时如何摆脱串行循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆