Все три — способы работы с асинхронным кодом. Колбэки — исторически первый подход, но ведут к "callback hell" при вложенности. Промисы решают эту проблему через цепочки .then() и стандартизированную обработку ошибок. async/await — синтаксический сахар над промисами, делающий асинхронный код похожим на синхронный. Важно: await всегда ждёт промис последовательно — для параллельности нужен Promise.all.
Первый способ асинхронности — передать функцию, которую надо вызвать "когда будет готово":
function fetchUser(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error('Неверный ID')) // ошибка — первым аргументом
} else {
callback(null, { id, name: 'Alice' }) // успех — вторым
}
}, 100)
}
// Node.js-стиль: первый аргумент — ошибка (error-first callback)
fetchUser(1, (err, user) => {
if (err) { console.error(err); return }
console.log(user)
})**Callback Hell** — проблема глубокой вложенности:
fetchUser(1, (err, user) => {
if (err) return handleError(err)
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err)
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err)
// Ещё глубже...
// "Pyramid of Doom"
})
})
})Проблемы колбэков: вложенность, обработка ошибок в каждом колбэке вручную, невозможность использовать try/catch, трудно читать и отлаживать.
Промис — объект, представляющий результат асинхронной операции. Три состояния:
pending (ожидание) → fulfilled (выполнен) → resolved value
→ rejected (отклонён) → error reasonfunction fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error('Неверный ID'))
else resolve({ id, name: 'Alice' })
}, 100)
})
}
// Цепочка — вместо вложенности:
fetchUser(1)
.then(user => fetchPosts(user.id)) // возвращает промис
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error(err)) // одна точка обработки ошибок
.finally(() => console.log('Завершено'))Ключевые методы:
// Параллельное выполнение — ждём ВСЕ
Promise.all([fetchA(), fetchB(), fetchC()])
.then(([a, b, c]) => console.log(a, b, c))
// Если хоть один rejected — весь Promise.all rejected
// Ждём первый успешный
Promise.any([fetchA(), fetchB()])
.then(result => console.log(result))
// Ждём первый завершённый (fulfilled или rejected)
Promise.race([fetchA(), timeout(5000)])
.then(result => console.log(result))
// Как Promise.all, но не падает при ошибке
Promise.allSettled([fetchA(), fetchB()])
.then(results => results.forEach(r => console.log(r.status)))Синтаксический сахар над промисами. async функция всегда возвращает промис. await приостанавливает выполнение до resolve/reject.
async function loadData() {
try {
const user = await fetchUser(1) // ждём промис
const posts = await fetchPosts(user.id) // ждём следующий
const comments = await fetchComments(posts[0].id)
return comments
} catch (err) {
console.error('Ошибка:', err)
throw err // пробрасываем если нужно
}
}
// async функция возвращает промис
loadData().then(data => console.log(data))// МЕДЛЕННО: последовательно (3 секунды если каждый по 1 сек)
async function sequential() {
const a = await fetchA() // 1 секунда
const b = await fetchB() // + 1 секунда
const c = await fetchC() // + 1 секунда
return [a, b, c] // = 3 секунды
}
// БЫСТРО: параллельно (1 секунда)
async function parallel() {
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()])
return [a, b, c] // = 1 секунда (все запросы одновременно)
}**Правило**: если операции не зависят друг от друга — всегда используй Promise.all.
// ОШИБКА 1: забытый await
async function bad() {
const user = fetchUser(1) // без await — получим промис, не данные!
console.log(user.name) // undefined
}
// ОШИБКА 2: не обработан rejected promise
fetchUser(-1).then(user => console.log(user))
// UnhandledPromiseRejection — всегда добавляй .catch()!
// ОШИБКА 3: await в forEach (не работает)
async function badLoop(ids) {
ids.forEach(async (id) => {
const user = await fetchUser(id) // forEach не ждёт!
console.log(user)
})
}
// ПРАВИЛЬНО: for...of с await
async function goodLoop(ids) {
for (const id of ids) {
const user = await fetchUser(id)
console.log(user)
}
}Колбэки → только в API которые их требуют (addEventListener, fs в Node.js)
Промисы → когда нужны Promise.all/race/any, явные цепочки
async/await → для большинства асинхронного кода — читаемо и понятно**Расскажи эволюцию**: "Сначала были колбэки → callback hell → промисы решили вложенность → async/await сделали код читаемым".
**Покажи одну задачу тремя способами** — это отличный способ продемонстрировать понимание. Загрузка пользователя через колбэк, потом промис, потом async/await.
**Обязательно упомяни**: разницу между последовательным await и Promise.all — это часто спрашивают как follow-up вопрос.
**Время ответа**: 4-5 минут.
1. **"async/await заменяет промисы"** — нет, async/await — это синтаксический сахар. Внутри всё равно промисы. await fetch(url) ждёт промис.
2. **Не знать про await в forEach** — это классический баг в реальном коде. Если не знаешь почему forEach не работает с async — это практический пробел.
3. **Не знать Promise.all** — если для параллельных запросов пишешь последовательные await — ты тормозишь приложение. Любой ревьюер это заметит.
Одна и та же задача: получить пользователя и его посты — через колбэки, затем промисы, затем async/await
// Имитация API с задержкой (без fetch — используем Promise)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const fakeDB = {
users: { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } },
posts: {
1: [{ id: 10, title: 'Первый пост' }, { id: 11, title: 'Второй пост' }],
2: [{ id: 20, title: 'Пост Bob' }]
}
}
// ===== СПОСОБ 1: КОЛБЭКИ =====
function getUserCb(id, callback) {
delay(50).then(() => {
const user = fakeDB.users[id]
if (!user) callback(new Error(`Пользователь ${id} не найден`))
else callback(null, user)
})
}
function getPostsCb(userId, callback) {
delay(50).then(() => {
const posts = fakeDB.posts[userId] || []
callback(null, posts)
})
}
console.log('=== Колбэки ===')
getUserCb(1, (err, user) => {
if (err) { console.error('Ошибка:', err.message); return }
console.log('Пользователь:', user.name)
getPostsCb(user.id, (err, posts) => {
if (err) { console.error('Ошибка:', err.message); return }
console.log('Постов:', posts.length)
// Если нужны комментарии — ещё один уровень...
// Это и есть callback hell
})
})
// ===== СПОСОБ 2: ПРОМИСЫ =====
function getUser(id) {
return delay(50).then(() => {
const user = fakeDB.users[id]
if (!user) throw new Error(`Пользователь ${id} не найден`)
return user
})
}
function getPosts(userId) {
return delay(50).then(() => fakeDB.posts[userId] || [])
}
console.log('\n=== Промисы ===')
getUser(1)
.then(user => {
console.log('Пользователь:', user.name)
return getPosts(user.id)
})
.then(posts => {
console.log('Постов:', posts.length)
return posts
})
.catch(err => console.error('Ошибка:', err.message))
.finally(() => console.log('Промис-цепочка завершена'))
// ===== СПОСОБ 3: ASYNC/AWAIT =====
async function loadUserWithPosts(userId) {
try {
const user = await getUser(userId)
console.log('\n=== async/await ===')
console.log('Пользователь:', user.name)
const posts = await getPosts(user.id)
console.log('Постов:', posts.length)
return { user, posts }
} catch (err) {
console.error('Ошибка:', err.message)
throw err
}
}
loadUserWithPosts(1).then(data => {
console.log('Готово:', data.user.name, '—', data.posts.length, 'постов')
})
// ===== ПАРАЛЛЕЛЬНО vs ПОСЛЕДОВАТЕЛЬНО =====
async function comparePerformance() {
await delay(200) // ждём предыдущие примеры
console.log('\n=== Параллельно vs Последовательно ===')
// Последовательно: 50ms + 50ms = ~100ms
const seqStart = Date.now()
const user1 = await getUser(1)
const user2 = await getUser(2)
console.log(`Последовательно: ${Date.now() - seqStart}ms`)
// Параллельно: max(50ms, 50ms) = ~50ms
const parStart = Date.now()
const [u1, u2] = await Promise.all([getUser(1), getUser(2)])
console.log(`Параллельно: ${Date.now() - parStart}ms`)
console.log('Пользователи:', u1.name, u2.name)
}
comparePerformance()Все три — способы работы с асинхронным кодом. Колбэки — исторически первый подход, но ведут к "callback hell" при вложенности. Промисы решают эту проблему через цепочки .then() и стандартизированную обработку ошибок. async/await — синтаксический сахар над промисами, делающий асинхронный код похожим на синхронный. Важно: await всегда ждёт промис последовательно — для параллельности нужен Promise.all.
Первый способ асинхронности — передать функцию, которую надо вызвать "когда будет готово":
function fetchUser(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error('Неверный ID')) // ошибка — первым аргументом
} else {
callback(null, { id, name: 'Alice' }) // успех — вторым
}
}, 100)
}
// Node.js-стиль: первый аргумент — ошибка (error-first callback)
fetchUser(1, (err, user) => {
if (err) { console.error(err); return }
console.log(user)
})**Callback Hell** — проблема глубокой вложенности:
fetchUser(1, (err, user) => {
if (err) return handleError(err)
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err)
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err)
// Ещё глубже...
// "Pyramid of Doom"
})
})
})Проблемы колбэков: вложенность, обработка ошибок в каждом колбэке вручную, невозможность использовать try/catch, трудно читать и отлаживать.
Промис — объект, представляющий результат асинхронной операции. Три состояния:
pending (ожидание) → fulfilled (выполнен) → resolved value
→ rejected (отклонён) → error reasonfunction fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error('Неверный ID'))
else resolve({ id, name: 'Alice' })
}, 100)
})
}
// Цепочка — вместо вложенности:
fetchUser(1)
.then(user => fetchPosts(user.id)) // возвращает промис
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error(err)) // одна точка обработки ошибок
.finally(() => console.log('Завершено'))Ключевые методы:
// Параллельное выполнение — ждём ВСЕ
Promise.all([fetchA(), fetchB(), fetchC()])
.then(([a, b, c]) => console.log(a, b, c))
// Если хоть один rejected — весь Promise.all rejected
// Ждём первый успешный
Promise.any([fetchA(), fetchB()])
.then(result => console.log(result))
// Ждём первый завершённый (fulfilled или rejected)
Promise.race([fetchA(), timeout(5000)])
.then(result => console.log(result))
// Как Promise.all, но не падает при ошибке
Promise.allSettled([fetchA(), fetchB()])
.then(results => results.forEach(r => console.log(r.status)))Синтаксический сахар над промисами. async функция всегда возвращает промис. await приостанавливает выполнение до resolve/reject.
async function loadData() {
try {
const user = await fetchUser(1) // ждём промис
const posts = await fetchPosts(user.id) // ждём следующий
const comments = await fetchComments(posts[0].id)
return comments
} catch (err) {
console.error('Ошибка:', err)
throw err // пробрасываем если нужно
}
}
// async функция возвращает промис
loadData().then(data => console.log(data))// МЕДЛЕННО: последовательно (3 секунды если каждый по 1 сек)
async function sequential() {
const a = await fetchA() // 1 секунда
const b = await fetchB() // + 1 секунда
const c = await fetchC() // + 1 секунда
return [a, b, c] // = 3 секунды
}
// БЫСТРО: параллельно (1 секунда)
async function parallel() {
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()])
return [a, b, c] // = 1 секунда (все запросы одновременно)
}**Правило**: если операции не зависят друг от друга — всегда используй Promise.all.
// ОШИБКА 1: забытый await
async function bad() {
const user = fetchUser(1) // без await — получим промис, не данные!
console.log(user.name) // undefined
}
// ОШИБКА 2: не обработан rejected promise
fetchUser(-1).then(user => console.log(user))
// UnhandledPromiseRejection — всегда добавляй .catch()!
// ОШИБКА 3: await в forEach (не работает)
async function badLoop(ids) {
ids.forEach(async (id) => {
const user = await fetchUser(id) // forEach не ждёт!
console.log(user)
})
}
// ПРАВИЛЬНО: for...of с await
async function goodLoop(ids) {
for (const id of ids) {
const user = await fetchUser(id)
console.log(user)
}
}Колбэки → только в API которые их требуют (addEventListener, fs в Node.js)
Промисы → когда нужны Promise.all/race/any, явные цепочки
async/await → для большинства асинхронного кода — читаемо и понятно**Расскажи эволюцию**: "Сначала были колбэки → callback hell → промисы решили вложенность → async/await сделали код читаемым".
**Покажи одну задачу тремя способами** — это отличный способ продемонстрировать понимание. Загрузка пользователя через колбэк, потом промис, потом async/await.
**Обязательно упомяни**: разницу между последовательным await и Promise.all — это часто спрашивают как follow-up вопрос.
**Время ответа**: 4-5 минут.
1. **"async/await заменяет промисы"** — нет, async/await — это синтаксический сахар. Внутри всё равно промисы. await fetch(url) ждёт промис.
2. **Не знать про await в forEach** — это классический баг в реальном коде. Если не знаешь почему forEach не работает с async — это практический пробел.
3. **Не знать Promise.all** — если для параллельных запросов пишешь последовательные await — ты тормозишь приложение. Любой ревьюер это заметит.
Одна и та же задача: получить пользователя и его посты — через колбэки, затем промисы, затем async/await
// Имитация API с задержкой (без fetch — используем Promise)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const fakeDB = {
users: { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } },
posts: {
1: [{ id: 10, title: 'Первый пост' }, { id: 11, title: 'Второй пост' }],
2: [{ id: 20, title: 'Пост Bob' }]
}
}
// ===== СПОСОБ 1: КОЛБЭКИ =====
function getUserCb(id, callback) {
delay(50).then(() => {
const user = fakeDB.users[id]
if (!user) callback(new Error(`Пользователь ${id} не найден`))
else callback(null, user)
})
}
function getPostsCb(userId, callback) {
delay(50).then(() => {
const posts = fakeDB.posts[userId] || []
callback(null, posts)
})
}
console.log('=== Колбэки ===')
getUserCb(1, (err, user) => {
if (err) { console.error('Ошибка:', err.message); return }
console.log('Пользователь:', user.name)
getPostsCb(user.id, (err, posts) => {
if (err) { console.error('Ошибка:', err.message); return }
console.log('Постов:', posts.length)
// Если нужны комментарии — ещё один уровень...
// Это и есть callback hell
})
})
// ===== СПОСОБ 2: ПРОМИСЫ =====
function getUser(id) {
return delay(50).then(() => {
const user = fakeDB.users[id]
if (!user) throw new Error(`Пользователь ${id} не найден`)
return user
})
}
function getPosts(userId) {
return delay(50).then(() => fakeDB.posts[userId] || [])
}
console.log('\n=== Промисы ===')
getUser(1)
.then(user => {
console.log('Пользователь:', user.name)
return getPosts(user.id)
})
.then(posts => {
console.log('Постов:', posts.length)
return posts
})
.catch(err => console.error('Ошибка:', err.message))
.finally(() => console.log('Промис-цепочка завершена'))
// ===== СПОСОБ 3: ASYNC/AWAIT =====
async function loadUserWithPosts(userId) {
try {
const user = await getUser(userId)
console.log('\n=== async/await ===')
console.log('Пользователь:', user.name)
const posts = await getPosts(user.id)
console.log('Постов:', posts.length)
return { user, posts }
} catch (err) {
console.error('Ошибка:', err.message)
throw err
}
}
loadUserWithPosts(1).then(data => {
console.log('Готово:', data.user.name, '—', data.posts.length, 'постов')
})
// ===== ПАРАЛЛЕЛЬНО vs ПОСЛЕДОВАТЕЛЬНО =====
async function comparePerformance() {
await delay(200) // ждём предыдущие примеры
console.log('\n=== Параллельно vs Последовательно ===')
// Последовательно: 50ms + 50ms = ~100ms
const seqStart = Date.now()
const user1 = await getUser(1)
const user2 = await getUser(2)
console.log(`Последовательно: ${Date.now() - seqStart}ms`)
// Параллельно: max(50ms, 50ms) = ~50ms
const parStart = Date.now()
const [u1, u2] = await Promise.all([getUser(1), getUser(2)])
console.log(`Параллельно: ${Date.now() - parStart}ms`)
console.log('Пользователи:', u1.name, u2.name)
}
comparePerformance()Преобразуй код на колбэках в async/await. Реализуй функцию loadDashboard, которая параллельно загружает пользователя, его настройки и уведомления, затем последовательно загружает посты пользователя (зависят от user.id).
async function loadDashboard(userId) { try { const user = await apiCall("getUser", {...}); const [settings, notifications] = await Promise.all([apiCall("getSettings", {...}), apiCall("getNotifications", {...})]); const postsData = await apiCall("getPosts", { userId: user.id, posts: [...] }); return { user, settings, notifications, posts: postsData.posts }; } catch(err) { throw err; } }
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке