Перевод заметки Mathias Bynens: Asynchronous stack traces: why await beats .then(). Опубликовано с разрешения автора.
В сравнении использованием промисов напрямую, async/await не только делают код более читабельным для разработчиков, но также добавляют некоторые оптимизации в движке JavaScript.
Фундаментальное отличие между await
и нативными промисами в том, что await X()
приостанавливает выполнение текущей функции, когда promise.then(X)
продолжает выполнение после добавления вызова X
в цепочку функций обратного вызова (callback). Для стектрейса эта разница весьма значительная.
В любой момент, когда цепочка промисов бросает необработанное исключение, движок JavaScript должен отобразить сообщение об ошибке и (вероятно) полезный стектрейс. Как разработчик, вы ожидаете этого, независимо от того, используете вы нативные промисы или же async/await
.
Представим сценарий, когда функция c
была вызвана в результате resolve
асинхронного выполнения функции b
.
const a = () => {
b().then(() => c());
};
Когда a
была вызвана, следующее было выполнено синхронно:
b
была вызвана и вернулаPromise
, который выполнилresolve
в некоторый момент времени в будущем.- функция обратного вызова
.then
, вызывающая функциюc
, была добавлена в цепочку (или, говоря на языке V8: «был добавлен, как «resolve handler»).
После этого мы завершаем выполнение кода в теле функции a
. a
никогда не приостанавливается, из-за чего мы теряем контекст в результате асинхронного вызова resolve
функции b
. Представьте, что будет, если b
(или c
) асинхронно бросит исключение. Стектрейс должен включать информацию о функции a
, так как источник исключения b
(или c
), правильно? Как это возможно, когда ссылка на a
потеряна?
Для того, чтобы этого избежать, движок JavaScript должен сделать кое-что помимо перечисленных выше шагов: он захватывает и хранит стектрейс в пределах функции a
до конца её выполнения. В V8 стектрейс привязан к промису, возвращенному от b
. По завершён.и промиса, стектрейс передаётся дальше и c
может использовать его по мере необходимости.
Захват стектрейса забирает время (следовательно, ухудшает производительность) и хранение стектрейса требует памяти.
Это та же программа, написанная с использованием async/await
вместо нативных промисов.
const a = async () => {
await b();
c();
};
C await
нет необходимости хранить текущий стектрейс — достаточно хранить указатель от b
на a
. Во время выполнения b
, a
приостанавливается, что позволяет всё ещё иметь доступ к текущему контексту. Если b
бросает исключение, стектрейс может быть восстановлен путём перемещения этих указателей. Если c
бросает исключение, стектрейс будет сформирован точно также, как это бы было в случае с синхронным выполнением, так как в это время мы все ещё находимся внутри функции a
. Так или иначе, захват стектрейса больше не нужен: вместо этого его формирование происходит тогда, когда это необходимо.
Большинство ECMAScript фич являются «просто синтаксическим сахаром», но async/await
- нечто большее.
Делайте так, чтобы JavaScript движок мог обрабатывать стектрейсы более производительным и менее затратным к памяти способом, следуя этим рекомендациям:
- Используйте
async/await
вместо нативных промисов. - Используйте babel-preset-env во избежание транспайлига
async/await
без необходимости.
Несмотря на то, что V8 ещё не реализует эту оптимизацию, следуя этим советам вы обеспечите оптимальную производительность, как только она появится.
Не используйте транспиляцию кода, пока вы действительно не испытываете в этом необходимость. Например, все современные браузеры, которые поддерживают сервис воркеры, также поддерживают async/await
. В следствии чего, нет необходимости в транспиляции этого кода в нативные промисы. То же касается и браузеров с поддержкой ES модулей. Для более детальной информации, смотрите статью Филлипа на deploying ES2015+ code in production today
Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.