Синхронно и асинхронно: в чём разница

асинхронность≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

У JavaScript одна пара рук: он делает ровно одно дело за раз. Поэтому вся разница между синхронным и асинхронным кодом — это разница между «стою и жду» и «займусь другим, позовите, когда будет готово».

Разделы темы:·

Движок выполняет ваш код строка за строкой, в одном-единственном потоке. Пока не досчитана текущая операция, следующая не начнётся — никакого «фонового выполнения» у обычного кода нет. Это упрощает жизнь: значения переменных не меняются сами по себе посреди функции.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Важная деталь: тот же самый поток обслуживает не только ваши вычисления. Он же обрабатывает клики, ввод текста, прокрутку и перерисовку страницы. Занят подсчётом суммы — значит, занят вообще всем.

«Заблокировать поток» — это занять его длинной синхронной работой. Со стороны кода всё нормально: цикл крутится, функция считает. Со стороны пользователя вкладка мертва: кнопка нажимается, но ничего не происходит, буквы не появляются в поле ввода, анимация замирает на месте, а если затянуть — браузер предложит остановить страницу.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ориентир простой: браузер перерисовывает картинку примерно 60 раз в секунду, то есть на один кадр у него около 16 мс. Синхронная задача длиннее — и кадр пропущен; дольше 100 мс — интерфейс уже ощущается как «подтормаживает».

16 мс — весь бюджет одного кадра. Всё, что дольше, пользователь видит глазами как рывок.

Не всякую работу делает сам JavaScript. Подождать секунду, скачать данные по сети, прочитать файл с диска — этим занимается окружение: браузер или Node.js. У них потоков много, и написаны они не на JavaScript. Ваш код только оставляет поручение и функцию, которую надо вызвать по готовности, — и идёт дальше, не дожидаясь.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Даже нулевая задержка не делает setTimeout мгновенным: колбэк в любом случае попадает в очередь и ждёт, пока поток договорит текущий сценарий до конца.

ОперацияКак ведёт себя
Циклы, математика, работа с массивамисинхронно — поток занят вами
setTimeout, setIntervalасинхронно — время отсчитывает браузер
fetch, загрузка картинкиасинхронно — запрос ведёт сеть браузера
fs.readFile в Node.jsасинхронно — чтение ведут служебные потоки Node.js
fs.readFileSync в Node.jsсинхронно — поток стоит и ждёт диск
alert, confirm, promptсинхронно — замораживают вкладку целиком

Представьте кафе, где бариста один. Синхронный бариста принял заказ на капучино, нажал кнопку на кофемашине и две минуты смотрит, как та работает. Очередь стоит, хотя сам он ничего не делает. Асинхронный бариста запускает машину, выдаёт номерок, принимает ещё пять заказов, а когда машина пикнула — отдаёт кофе. Кофе сварился ровно за те же две минуты, просто никто не простаивал.

Кофемашина здесь — это браузер, а не второй бариста. И у метафоры есть обратная сторона: если бариста решит десять минут вручную взбивать молоко, никакая асинхронность не спасёт — очередь встанет намертво. Долгий цикл в вашем коде — это ровно оно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Отсюда честная формулировка: setTimeout(f, 100) означает «не раньше чем через 100 мс», а не «ровно через 100 мс». Колбэк никогда не прерывает выполняющийся код на полуслове.

Три запроса в сеть действительно могут лететь одновременно — их ведёт браузер. Но обработчики их ответов выполнятся по очереди, один за другим, и никогда не столкнутся друг с другом. Асинхронность отвечает на вопрос «когда придёт результат», а не «сколько дел делается разом».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Браузер — это не только движок JavaScript. Внутри него есть сетевой модуль, таймеры, работа с диском и графикой, написанные на C++ и живущие в своих потоках. Единственный поток отдан вашему коду; всё остальное происходит рядом, и вы получаете только готовый результат — в виде вызова вашей функции.

Да — это Web Worker: отдельный поток с отдельным движком, которому можно отдать тяжёлые вычисления. Но общих переменных с основным кодом у него нет, доступа к странице тоже: только обмен сообщениями. Это уже другая тема: живой Worker мы запустим в части II, в разделе «Воркер — это поток, а не страница». А пока достаточно помнить, что по умолчанию поток один.

  • JavaScript выполняет код в одном потоке: одновременно делается ровно одна вещь, и этот же поток обслуживает клики и перерисовку страницы.
  • Синхронная работа блокирует всё: длинный цикл на секунду — это секунда мёртвого интерфейса; бюджет одного кадра всего около 16 мс.
  • Таймеры, сеть и работу с диском выполняет окружение — браузер или Node.js; ваш код оставляет колбэк и идёт дальше.
  • Колбэк не прерывает выполняющийся код, а ждёт очереди, поэтому setTimeout(f, 100) значит «не раньше чем через 100 мс».
  • Асинхронность не ускоряет саму работу и не даёт параллельности — она лишь не позволяет одному ожиданию тормозить всё остальное.

1/3Что выведет этот код?
console.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");

2/3Гарантирует ли setTimeout(f, 1000), что f выполнится ровно через секунду?

3/3Какая из трёхсекундных операций сделает страницу неотзывчивой?

Предскажи порядок вывода

Не запуская код, выпишите, в каком порядке появятся три строки. Потом проверьте себя.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Порядок такой:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Сначала выполняется весь синхронный код — поэтому «сразу» оказывается первым, хотя строка стоит последней. Оба вызова setTimeout только оставили поручение браузеру. Дальше колбэки приходят в порядке готовности таймеров: 0 мс раньше 100 мс.

Убери блокировку

Функция должна поздравлять через две секунды, но сейчас она эти две секунды держит поток занятым — соседняя строка печатается с опозданием. Перепишите так, чтобы вторая строка выводилась немедленно, а поздравление — через две секунды.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ждать должен браузер, а не движок. Заменяем цикл на таймер:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обратите внимание: pozdravit() теперь завершается мгновенно — она лишь заказывает вызов на будущее. Ждать те же две секунды никто не перестал, но всё это время поток свободен для кликов и перерисовки.