Skip to content

Latest commit

 

History

History

mathias-bynens-asynchronous-stack-traces-why-await-beats-then

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Асинхронные стектрейсы: почему await побеждает .then()

Перевод заметки Mathias Bynens: Asynchronous stack traces: why await beats .then(). Опубликовано с разрешения автора.

Асинхронные стектрейсы: почему await побеждает .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

Это та же программа, написанная с использованием 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.

Статья на Medium