Как вырваться из серийного цикла при использовании обещаний?

У меня есть длинный текстовый файл, который я прокручиваю по строкам, чтобы извлечь некоторые данные о событиях и сохранить их в базе данных. Файл периодически обновляется новыми данными вверху. Когда это произойдет, я снова запускаю файл, снова извлекая новые события, но я хочу остановиться, когда попаду в событие, которое уже находится в базе данных (файл всегда присваивается самым старым и старым).

Используя метод 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() в конце функции. Я использую библиотеку обещаний Bluebird 3.0 в 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() , которая promiseEach() каждый элемент массива. Если функция iteratorа возвращает promise, следующий элемент не обрабатывается до тех пор, пока это promise не будет устранено. Если iterator возвращает 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 строк.

То, что у вас на самом деле, вообще не выходит из цикла.

Каждый вызов Event.createAsync возвращается успешно с 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 Для Event.createAsync нет необходимости Event.createAsync исключение.

Способ визуализации разницы может заключаться в том, чтобы сравнить его с прокладкой дорожек для поезда, где трек представляет собой цепочку обещаний, а поезд представляет собой выполнение асинхронных операций, которые обещаны:

С reduce вы всегда кладете весь трек перед началом поезда, независимо от того, насколько поезд заканчивает движение по дорожке, прежде чем исключение остановит его. Каждый день вы едите затраты на прокладку всей дорожки (это может быть не так много, но это может сложить).

На примере recurse вы кладете следующий кусок трека точно в срок перед движущимся поездом, например, Громитом в финале «The Wrong Trousers» , поэтому нет времени тратить время на прокладку дорожек, которые не понадобятся ,