В JavaScript есть общий анти-шаблон:

function handleDataClb(err, data) {
    if(!data) throw new Error('no data found');
    // handle data... 
} 

function f() {
    try {
        fs.readFile('data', 'utf8', handleDataClb);
    } catch(e) {
        // handle error... 
    }
}

Этот try-catch в f не будет обнаруживать ошибки в handleDataClb, поскольку обратный вызов вызывается на более позднем этапе и в контексте, где try-catch больше не виден.

Теперь в JavaScript async-await реализован с использованием генераторов, обещаний и сопрограмм, как в:

// coroutine example 
co(function* doTask() {
    try {
        const res1 = yield asyncTask1(); // returns promise
        const res2 = yield asyncTask2(); // returns promise
        return res1 + res2;
    } catch(e) {
        // handle error... 
    }
});

// async-await example
async function doTask() {
    try {
        const res1 = await asyncTask1(); // returns promise
        const res2 = await asyncTask2(); // returns promise
        return res1 + res2;
    } catch(e) {
        // handle error... 
    }
}

Таким образом работает try-catch, который часто упоминается как одно из больших преимуществ async-await перед обратными вызовами.

Почему и как работает catch? Как сопрограмме, известной как async, удается выдать ошибку внутри try-catch, когда один из вызовов asyncTask приводит к отклонению обещания?

РЕДАКТИРОВАТЬ: как указывали другие, способ, которым движок JavaScript реализует оператор await, может сильно отличаться от чистой реализации JavaScript, используемой транспиляторами, такими как Babel и показанной выше как coroutine example. Поэтому, чтобы быть более конкретным: как это работает с использованием собственного JavaScript?

4
B M 21 Сен 2018 в 01:34

2 ответа

Лучший ответ

Почему и как работает catch? Как сопрограмме, известной как async, удается выдать ошибку внутри try-catch?

Выражение yield или await может иметь 3 разных результата:

  • Он может оценивать как простое выражение значение результата этого
  • Он может оцениваться как оператор throw, вызывая исключение.
  • Он может оцениваться как оператор return, вызывая оценку только операторов finally перед завершением функции.

В приостановленном генераторе этого можно добиться, вызвав либо .next(), .throw() или .return() методы. (Конечно, есть и четвертый возможный результат - никогда не возобновлять).

… Когда один из вызовов asyncTask приводит к отклонению обещания?

Значение await ed будет Promise.resolve() d для обещания, затем .then() метод вызывается для него с двумя обратными вызовами: когда обещание выполняется, сопрограмма возобновляется с нормальным значением (результатом обещания) и когда обещание отклоняется, сопрограмма возобновляется с внезапным завершением (исключение - причина отклонения).

Вы можете посмотреть код библиотеки co или вывод транспилятора - это буквально вызывает gen.throw из обратного вызова отклонения обещания.

1
Bergi 21 Сен 2018 в 21:39

async функции

Асинхронная функция возвращает обещание, которое разрешается значением, возвращаемым телом функции, или отклоняется ошибкой, выданной в теле.

Оператор await возвращает значение выполненного обещания или выдает ошибку, используя причину отклонения, если ожидаемое обещание отклонено.

Ошибки, выдаваемые await, могут быть перехвачены блоками try-catch внутри функции async вместо того, чтобы позволить им распространяться вверх по стеку выполнения и отклонять обещание, возвращаемое путем вызова функции async.

Оператор await также сохраняет контекст выполнения перед возвратом в цикл событий, чтобы разрешить выполнение операций обещания. При внутреннем уведомлении об исполнении ожидаемого обещания он восстанавливает контекст выполнения перед продолжением.

Блок try/catch, установленный в контексте выполнения функции async, не изменяется или не становится неэффективным просто потому, что контекст был сохранен и восстановлен await.

Как в сторону

"async-await реализован с использованием генераторов, обещаний и сопрограмм"

Может быть частью того, как Babel транслирует использование функций async и операторов await, но собственные реализации могут быть реализованы более напрямую.


Функции генератора

Контекст выполнения функции генератора хранится во внутреннем слоте [[Контекст генератора]] связанного с ним объекта-генератора. ( ECMA 2015 25.3.2 )

Выражения Yield удаляют контекст выполнения генератора из верхней части стека контекстов выполнения ( 25.3.3.5 из ES6 / ECMAScript 2015)

Возобновление функции генератора восстанавливает контекст выполнения функции из слота [[Контекст генератора]] объекта генератора.

Следовательно, функции генератора эффективно восстанавливают предыдущий контекст выполнения, когда возвращается выражение yield.

Выдача ошибки в функции генератора по нормальным причинам (синтаксическая ошибка, оператор throw, вызов функции, которая генерирует), может быть обнаружена блоком try-catch, как и ожидалось.

Выдача ошибки с помощью Generator.prototype.throw() вызывает ошибку в функции генератора, происходящую от yield expression, который последним передал управление от функции генератора. Эта ошибка может быть перехвачена try-catch как и для обычных ошибок. (Ссылка MDN с использованием throw ( ), ECMA 2015 25.3.3.4

< Сильный > Резюме

Блоки try-catch вокруг статистических данных yield, используемых в коде транспиляции await, работают по той же причине, что и для операторов await в собственных функциях async - они определены в одном выполнении context, поскольку ошибка возникает для отклоненного обещания.

0
traktor53 22 Сен 2018 в 06:55