Асинхронный JavaScript
В этом модуле мы рассмотрим asynchronous JavaScript, почему это важно, и как это поможет эффективно справляться с потенциальной блокировкой операций, таких как получение ресурсов с сервера или запись в файл.
Необходимые знания
Асинхронный JavaScript довольно сложная тема, и мы советуем пройти Первые шаги в JavaScript и Блоки в JavaScript прежде чем начать эту тему.
Если вы ещё не знакомы с концепциями асинхронного программирования, вам стоит начать со статьи Основные концепции асинхронного программирования в этом модуле. А если уже знакомы, то можете сразу переходить к статье Введение в асинхронный JavaScript.
Примечание: Если вы работаете за компьютером/планшетом/другим устройством где у вас нет возможности создавать собственные файлы, вы можете попробовать(почти все) примеры кода в одном из веб-приложений, таких, как JSBin или Thimble.
Руководства
В этой статье мы кратко расскажем о различиях между синхронным и асинхронным программированием, проблемах, связанных с синхронным JavaScript-ом, и взглянем на различные техники асинхронного программирования, с которыми вы столкнётесь. Покажем как эти техники помогают решать проблемы синхронного JavaScript.
Расскажем о промисах и том, как использовать API на их основе, а также объясним как работает функция с async и оператор await .
Статья о том, как реализовать собственный API на основе промисов.
Воркеры позволяют запускать код в отдельном потоке и не блокировать основной поток, чтобы код в нём оставался отзывчивым. В статье мы перепишем «тяжелую» синхронную функцию с использованием воркера.
Смотрите также
- Асинхронное программирование из фантастической онлайн книги Марина Хавербеке, Выразительный JavaScript.
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 3 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
1. Асинхронный JavaScript
При разработке часто приходится иметь дело с асинхронным поведением, которое может смущать новичков, у которых есть опыт работы с синхронным кодом.
- Синхронный код выполняется последовательно, каждая инструкция ожидает пока выполнится предыдущая.
- Асинхронный код выполняется не дожидаясь выполнения предыдущих инструкций.
1.1. Синхронный код
В синхронном коде, если есть две инструкции: L1 , за которой следует L2 , L2 не может начать выполнение до тех пор, пока L1 не завершит выполнение.
Представьте очередь покупки билетов на поезд. Вы не можете начать покупать билет на поезд до тех пор, пока не наступит ваша очередь. Точно так же люди, стоящие за вами, не могут начать покупать билеты до тех пор, пока не купите вы.
Этот код — синхронный, инструкции обрабатываются последовательно.
console.log('First'); console.log('Second'); console.log('Third');
1.2. Асинхронный код
В асинхронном коде, если есть две инструкции: L1 , за которой следует L2 , где L1 планирует выполнение какой-либо задачи в будущем, то L2 исполнится раньше чем L1 .
Представьте обед в ресторане. Вы и другие посетители, заказываете еду. Вам не нужно ждать, пока им принесут еду, прежде чем заказывать. Точно так же другие посетители не должны ждать, пока вы получите свое блюдо и поедите, прежде чем они смогут заказать. Каждый получит свое блюдо, как только его закончат готовить.
Следующий код — асинхронный. С функцией setTimeout мы познакомимся дальше. Сейчас о ней нам нужно знать только то, что она принимает 2 параметра, callback-функцию, которая будет вызвана по истечении времени, которое мы передаем вторым аргументом.
// выполнится первым console.log('First'); setTimeout(() => // выполнится третьим, спустя 2 секунды console.log('Second'); >, 2000); // выполнится вторым console.log('Third');
1.3. Асинхронность и многопоточность
Не путайте асинхронность и многопоточность. Многие думают, что многопоточность и асинхронность — это одно и то же, но это не так.
Приведем простую аналогию, которая все расставит по своим местам. Представьте, что вы шеф в ресторане и приходит заказ на яйца и тосты.
- Синхронный подход: сначала вы готовите яйца, затем тосты.
- Асинхронный подход (однопоточный): вы начинаете готовить яйца и устанавливаете таймер, затем вы начинаете готовить тосты и так же устанавливаете таймер. Пока яйца и тосты готовятся, вы убираете на кухне. Когда таймеры срабатывают, вы снимаете с огня яйца и достаете тосты из тостера и подаете их.
- Асинхронный подход (многопоточный): вы нанимаете еще двух поваров, одного для приготовления яиц и одного для приготовления тостов. Теперь у вас есть проблема управления поварами (потоками), чтобы они не конфликтовали друг с другом на кухне при совместном использовании ресурсов.
В асинхронных многопоточных процессах вы назначаете задачи работникам. В асинхронных однопоточных процессах у вас есть график задач, где некоторые задачи зависят от результатов других. По мере выполнения каждой задачи вызывается код, который планирует следующую задачу, которая может быть запущена с учетом результатов только что выполненной задачи. Но вам нужен только один работник для выполнения всех задач, а не один работник на задачу.
2. setTimeout и setInterval
Внутренний таймер-планировщик позволяет откладывать вызов функции на определенный период времени. Для этого есть таймауты и интервалы, которые позволяют контролировать когда и как часто вызывается функция.
Таймеры реализуются в браузере отдельно, а не встроенны в язык.
2.1. setTimeout и clearTimeout
Таймаут позволяет запускать функцию по истечении определенного времени.
const timerId = setTimeout(callback, delay, arg1, arg2, . );
- Принимает как минимум два аргумента: callback-функцию callback и время задержки delay в миллисекундах, по завершению которого один раз будет вызвана callback-функция.
- Дополнительные аргументы будут переданы callback-функции.
- Возвращает цифровой идентификатор созданного таймера, который используется для его удаления.
Если нам, по какой-то причине, нужно отменить вызов функции внутри таймаута, используется функция clearTimeout(id) , которая получает идентификатор таймера и очищает (удаляет) его.
function sayHello() console.log('Hello!'); > const timerId = setTimeout(sayHello, 5000); clearTimeout(timerId);
Поскольку мы использовали clearTimeout , а он исполнится раньше, таймер с timerId будет очищен и станет не активен. Он будет действовать так, как будто он никогда не существовал и поэтому в консоль ничего не выведется.
2.2. setInterval и clearInterval
Интервалы — это более простой способ повторения кода снова и снова с установленным промежутком времени повторений.

Функция setInterval имеет синтаксис, аналогичный setTimeout .
const timerId = setInterval(callback, delay, arg1, arg2, . );
Смысл аргументов тот же самый. Но, в отличие от setTimeout , она запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом clearInterval(id) .
2.3. this внутри функций-интервалов
setTimeout и setInterval — это методы глобального объекта window , именно поэтому мы не вызываем их как window.setTimeout() .
Необходимо быть внимательными с this внутри функций-интервалов. При классической callback-функции, this получит ссылку на window , а при стрелочной функции this будет из внешней области видимости.
2.4. Реальная частота срабатывания счетчика
У браузерного таймера есть минимальная возможная задержка. В современных браузерах она меняется от примерно 0мс до 4мс . В более старых она может быть больше и достигать 15мс . По стандарту, минимальная задержка составляет 4мс . Так что разницы между setTimeout(. 1) и setTimeout(. 4) нет.
В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами setInterval(. 4) может быть не 4мс , а 30мс или даже 1000мс . При слишком большой загрузке процессора некоторые запуски функций-интервалов будут пропущены.
Большинство браузеров, в первую очередь десктопных, продолжают выполнять setTimeout и setInterval , даже если вкладка неактивна. При этом ряд из них снижают минимальную частоту таймера до 1 раза в секунду. Получается, что в фоновой вкладке будет срабатывать таймер, но редко.
results matching » «
No results matching » «
Асинхронность Event loop

В JavaScript асинхронность — основной инструмент, который обрабатывает запросы параллельно с загрузкой веб-страницы. Сейчас невозможно представить интернет, где все запросы на сервер отправлялись бы с перезагрузкой страницы.
Любые данные от сервера запрашиваются асинхронно: отправляется запрос (XMLHttpRequest или XHR), и код не ждёт его возвращения, продолжая выполняться. Когда же сервер отвечает, объект XHR получает уведомление об этом и запускает функцию⚙️ обратного вызова — callback , который передали в него перед отправкой запроса.
Если правильно использовать инструменты языка , то выполнение запроса, который происходит последовательно и в одном потоке, никак не мешает приёму событий и реакции на них — человек спокойно работает с интерфейсом, не замечая лагов, сбоев и зависаний.
Видео
Event loop
Event loop в JavaScript — менеджер асинхронных вызовов.
Чтобы этот хитрый процесс слаженно работал, в JavaScript реализован механизм для управления очерёдностью исполнения кода . Поскольку это однопоточный язык , возникла необходимость «вклиниваться» в текущий контекст исполнения. Этот механизм называется event loop — событийный цикл.
С английского loop переводится как «петля», что отлично отражает смысл: мы имеем дело с закольцованной очередью.
Event loop регулирует последовательность исполнения контекстов — стек. Он формируется, когда сработало событие или была вызвана функция⚙️. Реакция на событие помещается в очередь исполнения, в event loop , который последовательно, с каждым циклом выполняет попадающий в него код . При этом привязанная к событию функция⚙️ вызывается следующей после текущего контекста исполнения.
В JavaScript постоянно работают связанные между собой синхронная и асинхронная очереди выполнения. Синхронная — stack — формирует очередь и пробрасывает в асинхронную — event loop — вызовы функций⚙️, которые будут выполнены после текущего запланированного исполняемого контекста.
Чтобы данные находились в консистентном состоянии, каждая функция⚙️ должна быть выполнена до конца. Это обусловлено однопоточностью JavaScript и некоторыми другими особенностями, например характерными для функциональных ⚙️языков программирования замыканиями. Поэтому единственный поток представлен в виде очереди контекстов исполнения, в которой и происходит «вклинивание» функций⚙️, прошедших через цикл событий.
Описание
JavaScript это однопоточный язык: одновременно может выполняться только одна задача. Обычно в этом нет ничего сложного, но теперь представьте, что вы запускаете задачу, которая занимает 30 секунд. Да. Во время этой задачи мы ждем 30 секунд, прежде чем что-либо еще может произойти (по умолчанию JavaScript запускается в главном потоке браузера, поэтому весь пользовательский интерфейс будет ждать) Сейчас 2021 год, никто не хочет медленный сайт который тупит.
К счастью, браузер предоставляет нам некоторые функции, которые сам механизм JavaScript не предоставляет: Web API. Который включает в себя DOM API, setTimeout, HTTP-запросы и так далее. Это может помочь нам создать асинхронное неблокирующее поведение .
Когда мы вызываем функцию, она добавляется в call stack(стек вызовов). Стек вызовов является частью механизма JS, это не зависит от браузера. Это классический взгляд на стек, т.е first in , last out . Когда функция возвращает значение, она «выталкивается» из стека.
function great() return 'Hello' > function respond() return setTimeout(() => alert('Hey!'), 1000) > great() respond()
Функция respond возвращает функцию setTimeout . SetTimeout предоставляется нам через Web-API : он позволяет нам делить задачи, не блокируя основной поток. Callback функция, которую мы передали в функцию setTimeout , лямбда функция () => добавляется в Web-API . Тем временем setTimeout и responde извлекаются из стека и возвращают свои значения.
В Web-API таймер работает до тех пор, пока второй аргумент, который мы передали ему, не подождет 1000 мс. Callback не сразу добавляется в стек вызовов, а передается в нечто, называемое очередью.
Это может сбивать с толку: это не означает, что callback функция добавляется в стек вызовов (таким образом, возвращает значение) через 1000 мс! Он просто добавляется в очередь через 1000 мс. Но в этой очереди, функция должна ждать пока придет ее черёд.
Теперь это та часть, которую мы все ждали. Время для event loop выполнить единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, то есть, если все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент в очереди добавляется в стек вызовов. В этом случае никакие другие функции не были вызваны, что означает, что стек вызовов был пуст к тому времени, когда callback функция была первым элементом в очереди.
callback добавляется в стек вызовов, вызывается и возвращает значение, а также извлекается из стека.
Смотреть весело, но вы не сможете полностью понять тему, не работая с ней снова и снова. Попробуйте выяснить, что появится в консоли, если мы запустим следующее:
const foo = () => console.log('First') const bar = () => setTimeout(() => console.log('Second'), 500) const baz = () => console.log('Third') bar() foo() baz()
Давайте посмотрим, что происходит, когда мы запускаем этот код в браузере:
Мы вызываем bar , которая возвращает функцию setTimeout . Callback который мы передали в setTimeout добавляется в Web API , функция setTimeout и bar извлекаются из стека вызовов.
Таймер запускается, тем временем foo вызывается и записывает в журнал First . foo возвращает undefined , baz вызывается и callback добавляется в очередь baz логирует Third . Цикл обработки событий видит, что коллстек пуст после возврата baz , после чего колбэк добавляется в стек вызовов. Callback логирует Second .
Надеюсь, что это заставит вас чувствовать себя более уверено с циклом событий event loop !
Не беспокойтесь, если это все еще кажется запутанным, самое важное — понять, откуда могут возникнуть определенные ошибки или специфическое поведение.

Проблемы?
Пишите в Discord или телеграмм чат, а также подписывайтесь на наши новости
Вопросы:
- Инструмент, который выводит контекст исполнения функции из синхронного потока
- Инструмент, который исполняет код построчно
- Инструмент, который обрабатывает запросы параллельно с загрузкой веб-страниц
Менеджер асинхронных вызовов:
- stack
- Event loop
- Объекты высшего класса
Инструмент, выполняющий код с задержкой в миллисекундах:
Для того чтобы понять, на сколько вы усвоили этот урок, пройдите тест в мобильном приложении нашей школы по этой теме или в нашем телеграм боте.

Ссылки:
- Объяснение работы EventLoop в JavaScript
- Как управлять event loop в JavaScript
- Справочник javascript
- Статья: Объяснение Event Loop в Javascript с помощью визуализации
- Статья: JavaScript Visualized: Promises & Async/Await
Contributors ✨
Thanks goes to these wonderful people (emoji key):
Async/await
Существует специальный синтаксис для работы с промисами, который называется «async/await». Он удивительно прост для понимания и использования.
Асинхронные функции
Начнём с ключевого слова async . Оно ставится перед функцией, вот так:
async function f()
У слова async один простой смысл: эта функция всегда возвращает промис. Значения других типов оборачиваются в завершившийся успешно промис автоматически.
Например, эта функция возвратит выполненный промис с результатом 1 :
async function f() < return 1; >f().then(alert); // 1
Можно и явно вернуть промис, результат будет одинаковым:
async function f() < return Promise.resolve(1); >f().then(alert); // 1
Так что ключевое слово async перед функцией гарантирует, что эта функция в любом случае вернёт промис. Согласитесь, достаточно просто? Но это ещё не всё. Есть другое ключевое слово – await , которое можно использовать только внутри async -функций.
Await
// работает только внутри async–функций let value = await promise;
Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится. После чего оно вернёт его результат, и выполнение кода продолжится.
В этом примере промис успешно выполнится через 1 секунду:
async function f() < let promise = new Promise((resolve, reject) => < setTimeout(() =>resolve("готово!"), 1000) >); let result = await promise; // будет ждать, пока промис не выполнится (*) alert(result); // "готово!" > f();
В данном примере выполнение функции остановится на строке (*) до тех пор, пока промис не выполнится. Это произойдёт через секунду после запуска функции. После чего в переменную result будет записан результат выполнения промиса, и браузер отобразит alert-окно «готово!».
Обратите внимание, хотя await и заставляет JavaScript дожидаться выполнения промиса, это не отнимает ресурсов процессора. Пока промис не выполнится, JS-движок может заниматься другими задачами: выполнять прочие скрипты, обрабатывать события и т.п.
По сути, это просто «синтаксический сахар» для получения результата промиса, более наглядный, чем promise.then .
await нельзя использовать в обычных функциях
Если мы попробуем использовать await внутри функции, объявленной без async , получим синтаксическую ошибку:
