我有一个很长的文本文件,我逐行循环以提取一些事件数据并将其存储在数据库中。该文件定期更新,顶部有新数据。当发生这种情况时,我会再次运行该文件来提取新事件,但是当我到达已经在数据库中的事件时,我想要停止(文件始终从最新到最旧)。
使用问题this answer 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()
数据库方法返回保存事件时已解决的承诺。如果事件已存在于数据库中,它将抛出异常,这将停止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
,则循环结束,Lo-dash样式:
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行文件上运行它会是否会调用堆栈问题。
答案 0 :(得分:4)
你所拥有的东西实际上并没有完全脱离循环。
对Event.createAsync
的每次调用都会立即成功返回,这意味着您总是会减少整个数组。
此循环生成的promise链的长度因此始终是文件中的总行数,减去既不符合特定逻辑中的日期和事件模式的行数。
这个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
解析时,只有在需要时才会异步扩展promise链。你也可以停止调用recurse
来停止,即Event.createAsync
不需要抛出异常来突破。
可视化差异的方法可能是将其与列车的轨道进行比较,其中轨道代表承诺链,而列车代表异步操作的执行。承诺:
使用reduce
时,您始终会在列车开始前首先放下整个轨道,无论列车在异常停止之前最终沿着轨道走多远。你每次都要花掉铺设整条赛道的费用(可能不多,但可以加起来)。
在recurse
示例中,您正在移动的火车前准时铺设下一条轨道,如Gromit in the finale of "The Wrong Trousers",所以没有时间浪费铺设不会有的轨道需要。