Синхронный vs асинхронный код
Почему JavaScript однопоточный
JavaScript создавался для браузера — добавлять интерактивность на веб-страницы. Один поток означает, что не нужно думать о блокировках, мьютексах и гонках данных. Любой код выполняется от начала до конца без прерывания другим кодом — это свойство называется run-to-completion.
let x = 1
x = x + 1
console.log(x) // 2 — гарантированно, никто не изменит x между строками
Один поток — это просто. Но есть проблема.
Блокирующий код
Если операция занимает время (сетевой запрос, чтение файла, тяжёлые вычисления), весь поток стоит и ждёт:
// ❌ Блокирующий подход (псевдокод)
const data = fetchSync('/api/users') // 2 секунды ожидания
// Всё это время: UI заморожен, кнопки не нажимаются, анимации стоят
renderUsers(data)
В браузере JavaScript работает в одном потоке с UI — блокирующий код замораживает весь интерфейс. Пользователь видит «зависшую» страницу.
Асинхронный подход
Вместо ожидания JavaScript делегирует долгую операцию среде выполнения (браузеру или Node.js) и продолжает работу:
// ✅ Асинхронный подход
fetch('/api/users') // отправляем запрос — не ждём
.then(r => r.json()) // когда ответ придёт — обработаем
.then(data => renderUsers(data))
console.log('Страница работает!') // выполняется сразу, не ждёт fetch
Ключевая идея: не блокировать поток. Запустил операцию → занимайся другим → получи результат когда готов.
Аналогия с кофейней
Представь кофейню с одним бариста:
Синхронный подход: бариста принимает заказ, делает кофе, отдаёт, и только потом берёт следующий заказ. Очередь стоит.
Асинхронный подход: бариста принимает заказ, ставит кофемашину, пока кофе готовится — принимает следующий заказ. Когда кофе готов — отдаёт. Один бариста обслуживает много клиентов.
JavaScript — этот бариста. Один поток, но благодаря делегированию работы (Web API, libuv) он не простаивает.
Что делает среда выполнения
JavaScript сам по себе не умеет делать сетевые запросы или читать файлы. Это делает среда выполнения:
- Браузер — предоставляет Web API:
fetch,setTimeout,addEventListener, DOM - Node.js — предоставляет свои API:
fs,http,net, основанные на библиотеке libuv
Когда ты вызываешь fetch() или setTimeout(), JavaScript отдаёт работу среде, а сам продолжает выполнять код. Когда операция завершается — результат возвращается через систему очередей (event loop), которую мы разберём в следующем разделе.
Эволюция асинхронного кода в JS
// 1. Callbacks (1995+)
readFile('data.txt', function(err, data) {
if (err) handleError(err)
parseData(data, function(err, result) {
if (err) handleError(err)
// ... и так далее — "callback hell"
})
})
// 2. Promises (ES2015)
readFile('data.txt')
.then(data => parseData(data))
.then(result => display(result))
.catch(err => handleError(err))
// 3. async/await (ES2017)
try {
const data = await readFile('data.txt')
const result = await parseData(data)
display(result)
} catch (err) {
handleError(err)
}
Каждый шаг — синтаксический сахар над предыдущим. Под капотом всё это работает на event loop — его мы разберём в следующем разделе.
Итого
| Факт | Описание |
|---|---|
| Однопоточность | Один поток, run-to-completion, нет гонок данных |
| Проблема | Блокирующий код замораживает UI |
| Решение | Делегирование долгих операций среде выполнения |
| Среда выполнения | Браузер (Web API) или Node.js (libuv) — выполняют I/O за пределами потока JS |
| Эволюция | Callbacks → Promises → async/await |