Какой механизм позволяет делать асинхронные операции в JavaScript?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм асинхронных операций в JavaScript
Asynchronous JavaScript полагается на несколько механизмов: Event Loop, Callback Queue, Promises, async/await. Это сложная система, которая позволяет выполнять долгие операции без блокировки интерфейса.
1. Call Stack и Event Loop
JavaScript работает в один поток (single-threaded). Event Loop управляет выполнением кода:
console.log('1. Start');
setTimeout(() => {
console.log('2. Timeout callback');
}, 0);
console.log('3. End');
// Порядок вывода:
// 1. Start
// 3. End
// 2. Timeout callback
// Даже с timeout = 0, callback выполнится ПОСЛЕ синхронного кода
Почему так? Потому что setTimeout добавляет callback в Callback Queue, а Event Loop сначала выполняет весь Call Stack.
Call Stack: [console.log('Start'), setTimeout, console.log('End')]
Callback Queue: [console.log('Timeout callback')]
Event Loop ждёт пока Call Stack пуст, потом берёт из Queue
2. Callbacks
Первый способ работать с асинхронностью — использовать callbacks:
function loadUser(id, callback) {
setTimeout(() => {
const user = { id, name: 'John' };
callback(user);
}, 1000);
}
loadUser(1, (user) => {
console.log('User loaded:', user);
// "Callback Hell" — вложенные callback'и
loadUser(user.id + 1, (user2) => {
console.log('User 2:', user2);
loadUser(user2.id + 1, (user3) => {
console.log('User 3:', user3);
// Код становится нечитаемым
});
});
});
Проблемы:
- Сложная читаемость (Callback Hell)
- Трудно обрабатывать ошибки
- Трудно управлять потоком выполнения
3. Promises
Промисы решили проблему callback'ов и предоставляют лучший способ работать с асинхронностью:
function loadUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: 'John' });
} else {
reject(new Error('Invalid ID'));
}
}, 1000);
});
}
// Использование
loadUser(1)
.then(user => {
console.log('User:', user);
return loadUser(user.id + 1);
})
.then(user2 => {
console.log('User 2:', user2);
return loadUser(user2.id + 1);
})
.then(user3 => console.log('User 3:', user3))
.catch(error => console.error('Error:', error));
Promise states:
const promise = new Promise((resolve, reject) => {
// pending — начальное состояние
setTimeout(() => {
resolve('Success'); // fulfilled — успешно
// или
reject(new Error('Failed')); // rejected — ошибка
}, 1000);
});
// После resolve/reject состояние не изменяется
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
Microtask Queue
Есть два типа очередей: Callback Queue и Microtask Queue. Promises используют Microtask Queue, которая приоритетнее:
console.log('1. Start');
setTimeout(() => {
console.log('5. setTimeout callback'); // Callback Queue
}, 0);
Promise.resolve()
.then(() => console.log('2. Promise 1')) // Microtask Queue
.then(() => console.log('3. Promise 2')); // Microtask Queue
console.log('4. End');
// Порядок:
// 1. Start
// 4. End
// 2. Promise 1 (Microtask очищается первой)
// 3. Promise 2
// 5. setTimeout callback (Callback Queue очищается после)
4. Async/Await
Это синтаксический сахар над Promises, который делает асинхронный код похожим на синхронный:
async function loadUsers() {
try {
const user1 = await loadUser(1);
console.log('User 1:', user1);
const user2 = await loadUser(user1.id + 1);
console.log('User 2:', user2);
const user3 = await loadUser(user2.id + 1);
console.log('User 3:', user3);
return [user1, user2, user3];
} catch (error) {
console.error('Error:', error);
}
}
// async функция всегда возвращает Promise
loadUsers().then(users => console.log('All users:', users));
5. Параллельные операции
Последовательное выполнение (один за другим)
async function sequential() {
const user1 = await loadUser(1); // 1000ms
const user2 = await loadUser(2); // 1000ms
const user3 = await loadUser(3); // 1000ms
// Итого: ~3000ms
}
Параллельное выполнение (одновременно)
async function parallel() {
// Promise.all завершается когда все промисы выполнены
// или когда первый отклонён
const [user1, user2, user3] = await Promise.all([
loadUser(1),
loadUser(2),
loadUser(3)
]);
// Итого: ~1000ms
}
// Promise.allSettled завершается когда все промисы settled
// (не отклоняет при ошибке)
const results = await Promise.allSettled([
loadUser(1),
loadUser(2),
loadUser('invalid')
]);
console.log(results); // [
// { status: 'fulfilled', value: {...} },
// { status: 'fulfilled', value: {...} },
// { status: 'rejected', reason: Error }
// ]
6. Web APIs
Внешние API (setTimeout, fetch, XMLHttpRequest) делегируют работу браузеру:
console.log('1. Start');
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('3. Data:', data));
console.log('2. End');
// Поток:
// 1. Call Stack: выполняется синхронно
// 2. fetch вызывает Web API
// 3. Web API в фоне делает запрос
// 4. Когда ответ придёт, Promise callback идёт в Microtask Queue
// 5. Event Loop выполняет callback
7. Практический пример
class DataFetcher {
private cache = new Map();
async fetchUser(id: number) {
if (this.cache.has(id)) {
return this.cache.get(id);
}
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Not found');
const data = await response.json();
this.cache.set(id, data);
return data;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
async fetchUsers(ids: number[]) {
// Параллельная загрузка
return Promise.all(ids.map(id => this.fetchUser(id)));
}
async fetchUserWithDetails(id: number) {
// Последовательная загрузка
const user = await this.fetchUser(id);
const posts = await fetch(`/api/users/${id}/posts`)
.then(r => r.json());
return { ...user, posts };
}
}
// Использование
const fetcher = new DataFetcher();
// Параллельная загрузка нескольких пользователей
const users = await fetcher.fetchUsers([1, 2, 3]);
// С обработкой ошибок
try {
const userWithDetails = await fetcher.fetchUserWithDetails(1);
console.log(userWithDetails);
} catch (error) {
console.error('Error:', error);
}
8. Обработка ошибок
// Promise catch
fetch('/api/data')
.then(r => r.json())
.catch(error => console.error('Error:', error))
.finally(() => console.log('Request complete'));
// Async/await try-catch
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error; // пробросить дальше если нужно
} finally {
console.log('Request complete');
}
}
Временная шкала выполнения
console.log('Script start');
setTimeout(() => console.log('setTimeout 1'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
fetch('/api/data')
.then(() => console.log('fetch response'));
console.log('Script end');
// Вывод:
// Script start
// Script end
// Promise 1 (Microtask Queue)
// Promise 2 (Microtask Queue)
// fetch response (Microtask Queue)
// setTimeout 1 (Callback Queue)
Вывод
Asynchronous JavaScript опирается на:
- Event Loop — управляет выполнением
- Call Stack — синхронный код
- Web APIs — делегирование браузеру
- Microtask Queue — Promises (приоритетнее)
- Callback Queue — setTimeout, события
МодернВ современном коде используйте async/await вместо callbacks и Promises, так как это более читаемо и удобнее обрабатывать ошибки.