Подтвердить что ты не робот

Генераторы 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.

4b9b3361

Ответ 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