使用承诺时如何打破串行循环?
How to break out of a serial loop when using promises?
我有一个长文本文件,我逐行循环以提取一些事件数据并将其存储在数据库中。该文件会定期更新顶部的新数据。发生这种情况时,我 运行 通过文件再次提取新事件,但我想在遇到数据库中已有的事件时停止(文件始终按最新到最旧的顺序排列)。
使用 this answer to the question Correct way to write loops for promise 中描述的 reduce()
方法,我想出了这个函数来解析文件:
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()
数据库方法 returns 保存事件时解决的承诺。如果事件已经存在于数据库中,它将抛出异常,这会停止承诺链,因此文件的其余部分不会被解析。该异常被函数末尾的 catch()
处理程序捕获并忽略。我在 Node.js.
中使用 Bluebird 3.0 promise 库
此函数会连续循环遍历每一行,并在遇到已保存的事件时正确停止。但我想知道这是否是处理承诺时跳出循环的最佳方式。在函数末尾吞下抛出的异常似乎有点笨拙。
欢迎提出任何改进循环处理的建议。
解决方案?
建立在 , and taking into account 的基础上,也许我应该尝试他对我链接到的问题的非减少答案:),我想出了这个解决方案:
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()
中,该函数遍历数组中的每个项目。如果迭代器函数 returns 一个承诺,则在该承诺解决之前不会处理下一个项目。如果迭代器returnsfalse
,则循环结束,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 行文件是否会出现调用堆栈问题。
你所拥有的实际上根本没有跳出循环。
每次调用 Event.createAsync
returns 都会立即成功,这意味着您总是减少 整个 数组。
因此,此循环生成的承诺链的长度将始终是文件中的总行数,减去既不适合您的特定逻辑中的日期也不适合事件模式的行数。
这是此 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",因此不会浪费时间铺设轨道不需要。
我有一个长文本文件,我逐行循环以提取一些事件数据并将其存储在数据库中。该文件会定期更新顶部的新数据。发生这种情况时,我 运行 通过文件再次提取新事件,但我想在遇到数据库中已有的事件时停止(文件始终按最新到最旧的顺序排列)。
使用 this answer to the question Correct way to write loops for promise 中描述的 reduce()
方法,我想出了这个函数来解析文件:
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()
数据库方法 returns 保存事件时解决的承诺。如果事件已经存在于数据库中,它将抛出异常,这会停止承诺链,因此文件的其余部分不会被解析。该异常被函数末尾的 catch()
处理程序捕获并忽略。我在 Node.js.
此函数会连续循环遍历每一行,并在遇到已保存的事件时正确停止。但我想知道这是否是处理承诺时跳出循环的最佳方式。在函数末尾吞下抛出的异常似乎有点笨拙。
欢迎提出任何改进循环处理的建议。
解决方案?
建立在
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()
中,该函数遍历数组中的每个项目。如果迭代器函数 returns 一个承诺,则在该承诺解决之前不会处理下一个项目。如果迭代器returnsfalse
,则循环结束,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 行文件是否会出现调用堆栈问题。
你所拥有的实际上根本没有跳出循环。
每次调用 Event.createAsync
returns 都会立即成功,这意味着您总是减少 整个 数组。
因此,此循环生成的承诺链的长度将始终是文件中的总行数,减去既不适合您的特定逻辑中的日期也不适合事件模式的行数。
这是此 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",因此不会浪费时间铺设轨道不需要。