Генераторы ES6: неудачная трассировка стека из iterator.throw(err)
Метод ES6: iterator.throw(err)
часто описывается как внедрение исключения, как если бы оно произошло в операторе yield
в генераторе. Проблема заключается в том, что трассировка стека для этого исключения не содержит ссылки на файл/строку для оператора yield или даже на функцию, в которой он находится. Скорее, трассировка стека создается только при создании объекта исключения, которого нет внутри generator
.
Вопрос заключается в следующем: как я могу получить местоположение ошибочного оператора yield в трассировке стека или иным образом?
function* one_of_many_generators() {
// ...
yield ajax(url); // <-- what I need in the stack trace
// ...
}
function outer() {
var iterator = one_of_many_generators();
iterator.next(); // runs to the first yield
// inject exception at the yield statement
iterator.throw(Error("error")); // <-- top of stack trace shows here
}
Хотя эта проблема не относится только к Promises
, они могут облегчить картину проблемы. В моем случае я использую систему задач с генераторами и обещаниями. Гипотетическая функция ajax()
возвращает Promise, и если она отклонена, ошибка преобразуется в throw в операторе yield с использованием этого механизма.
Трассировки стека в отладчике довольно бесполезны, потому что я не могу найти способ получить функцию, файл или номер строки для yield statement
где происходит это внедрение. Вызов iterator.throw(err)
обрабатывается как rethrow и не получает новую информацию о стеке, поэтому он показывает только местоположение внутри функции ajax()
которую можно вызывать из многих мест, и выдает новую ошибку в outer()
как в примере выше, одна и та же строка броска показывает для всех ошибок. Ни один из них не дает подсказки о том, какая функция generator
выполнялась для отладки ошибки.
Я использую Chrome v42.
Ответы
Ответ 1
Итераторы и promises не смешиваются очень хорошо (пока) - вы по существу даете обещание, которое затем выходит из строя вне цикла.
Вы можете обойти это, передав результаты обещания обратно генератору, например:
function* one_of_many_generators() {
// ...
var promiseResult = yield ajax(url); // <-- what I need in the stack trace
// Now we're back in the generator with the result of the promise
if(notHappyWithResult(promiseResult))
throw new Error('Reason result is bad');
// ...
}
async function outer() {
var iterator = one_of_many_generators();
let prms = iterator.next(); // runs to the first yield
// Wait for the promise to finish
let result = await prms;
// Pass the result back to the generator
let whatever = iterator.next(result);
}
Только: это то, что async
и await
делать в любом случае (эти ключевые слова являются просто синтаксическим сахаром для генератора promises с возвращенными результатами), и если вы используете их, будет выполняться обычный try-catch
.
iterator.throw
- это, в основном, способ остановить итерацию, не вставляя в нее исключение - вершина стека все еще находится там, где вы создаете Error
.
Наконец, скоро в Chrome будут асинхронные итераторы - они довольно мощные и все о итерациях promises.
Ответ 2
Как насчет этого подхода:
async function example() {
const arrayOfFetchPromises = [
fetch('1.txt'),
fetch('2.txt'),
fetch('3.txt')
];
// Regular iterator:
for (const item of arrayOfFetchPromises) {
console.log(item); // Logs a promise
}
// Async iterator:
for await (const item of arrayOfFetchPromises) {
console.log(item); // Logs a response
}
}
В этом случае for-await
берет каждый элемент из array
и ожидает его разрешения. Вы получите первый ответ, даже если второй ответ еще не готов, но вы всегда получите ответы в правильном порядке. Вы можете просто обработать отклонение, например, используя тот же старый .catch
:) Но известно, что этот шаблон подвержен необработанным отклонениям. Там всегда Promise.all
... Версия Promise.all:
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
Я также предлагаю проверить некоторые из этих инструментов: iter-tools