Открываешь GitHub — страница начинает загружать сразу всё: твой профиль, репозитории, уведомления, pull requests. JavaScript однопоточный, но браузер умеет делать сетевые запросы «в фоне». Вопрос: как получить результат, когда он придёт?
Раньше использовали колбэки — и получали «ад колбэков»:
// Callback hell — каждый уровень требует ещё одного колбэка
getUser(id, function(user) {
getRepos(user.login, function(repos) {
getCommits(repos[0].id, function(commits) {
render(user, repos, commits) // вложенность растёт до бесконечности
}, onError)
}, onError)
}, onError)Промис (Promise) — объект-обёртка для асинхронной операции. Он позволяет строить цепочки вместо пирамид вложенности.
resolve(value) был вызванreject(error) был вызванСостояние меняется только один раз и необратимо.
const loadUser = new Promise((resolve, reject) => {
// Асинхронная операция
setTimeout(() => {
const user = { id: 1, name: 'Иван' }
if (user) {
resolve(user) // передаём результат
} else {
reject(new Error('Не найден')) // передаём ошибку
}
}, 300)
})loadUser
.then(user => {
console.log('Пользователь:', user.name)
return user.id // возвращаем значение для следующего .then
})
.then(id => console.log('ID:', id)) // получаем то, что вернул предыдущий .then
.catch(e => console.error('Ошибка:', e.message)) // ловим любую ошибку из цепочки
.finally(() => console.log('Загрузка завершена')) // всегдаКлючевой момент: каждый .then() возвращает новый промис. Это позволяет строить цепочки.
// Вместо вложенных колбэков — плоская цепочка
getUser(1)
.then(user => getRepos(user.login)) // возвращаем промис
.then(repos => getCommits(repos[0].id)) // получаем его результат
.then(commits => render(commits))
.catch(e => showError(e.message))// Уже выполненный промис — удобно в тестах и дефолтных значениях
const p1 = Promise.resolve({ id: 1, name: 'Тест' })
p1.then(user => console.log(user.name)) // 'Тест'
// Уже отклонённый промис
const p2 = Promise.reject(new Error('Сервер недоступен'))
p2.catch(e => console.log(e.message)) // 'Сервер недоступен'Запускает несколько промисов одновременно и ждёт все:
// Вместо последовательных 300мс + 200мс + 150мс = 650мс...
// Параллельно: max(300, 200, 150) = 300мс
const [user, repos, notifications] = await Promise.all([
fetchUser(id), // 300мс
fetchRepos(id), // 200мс
fetchNotifications(id), // 150мс
])
// Всё пришло через 300мс!Если хотя бы один промис отклоняется — весь Promise.all отклоняется немедленно.
1. Не возвращают промис из .then():
// Сломано — вложенный промис, а не цепочка:
getUser(1)
.then(user => {
getRepos(user.id) // нет return — следующий .then не получит repos!
})
.then(repos => console.log(repos)) // repos === undefined
// Исправлено:
getUser(1)
.then(user => getRepos(user.id)) // return обязателен
.then(repos => console.log(repos.length))2. Забывают .catch():
// Сломано — ошибка "улетит" без обработки (UnhandledPromiseRejection):
getUser(1)
.then(user => render(user))
// нет .catch — если getUser упадёт, узнаем только из логов Node.js
// Исправлено:
getUser(1)
.then(user => render(user))
.catch(e => showErrorMessage(e.message))3. Создают Promise там, где он уже есть:
// Сломано — лишняя обёртка:
function getUser(id) {
return new Promise((resolve, reject) => {
fetch(`/api/users/${id}`)
.then(r => r.json())
.then(resolve)
.catch(reject)
})
}
// Исправлено — fetch уже возвращает промис:
function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json())
}useEffect с промисами для загрузки данныхAPI GitHub: цепочка промисов и параллельная загрузка данных
// Симуляция GitHub API
function fetchUser(login) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!login) return reject(new Error('Login обязателен'))
resolve({ login, name: 'Иван Петров', publicRepos: 42, followers: 318 })
}, 100)
})
}
function fetchRepos(login) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ name: 'awesome-project', stars: 124, lang: 'JavaScript' },
{ name: 'my-blog', stars: 18, lang: 'TypeScript' },
{ name: 'utils-lib', stars: 67, lang: 'JavaScript' },
])
}, 150)
})
}
function fetchFollowers(login) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ login: 'anna', name: 'Анна Смирнова' },
{ login: 'boris', name: 'Борис Иванов' },
])
}, 80)
})
}
// Цепочка: получаем пользователя, потом его репозитории
console.log('Загружаем профиль...')
fetchUser('ivanov')
.then(user => {
console.log(`${user.name}: ${user.publicRepos} репозиториев`)
return fetchRepos(user.login) // возвращаем промис — цепочка продолжается
})
.then(repos => {
const topRepo = repos.reduce((top, r) => r.stars > top.stars ? r : top)
console.log(`Топ репо: ${topRepo.name} (${topRepo.stars} ⭐)`)
return topRepo
})
.then(repo => console.log(`Язык: ${repo.lang}`))
.catch(e => console.error('Ошибка:', e.message))
.finally(() => console.log('Загрузка завершена'))
// Promise.all — параллельная загрузка репозиториев и подписчиков
Promise.all([
fetchRepos('ivanov'),
fetchFollowers('ivanov'),
])
.then(([repos, followers]) => {
console.log('Репозиториев:', repos.length) // 3
console.log('Подписчиков:', followers.length) // 2
})Открываешь GitHub — страница начинает загружать сразу всё: твой профиль, репозитории, уведомления, pull requests. JavaScript однопоточный, но браузер умеет делать сетевые запросы «в фоне». Вопрос: как получить результат, когда он придёт?
Раньше использовали колбэки — и получали «ад колбэков»:
// Callback hell — каждый уровень требует ещё одного колбэка
getUser(id, function(user) {
getRepos(user.login, function(repos) {
getCommits(repos[0].id, function(commits) {
render(user, repos, commits) // вложенность растёт до бесконечности
}, onError)
}, onError)
}, onError)Промис (Promise) — объект-обёртка для асинхронной операции. Он позволяет строить цепочки вместо пирамид вложенности.
resolve(value) был вызванreject(error) был вызванСостояние меняется только один раз и необратимо.
const loadUser = new Promise((resolve, reject) => {
// Асинхронная операция
setTimeout(() => {
const user = { id: 1, name: 'Иван' }
if (user) {
resolve(user) // передаём результат
} else {
reject(new Error('Не найден')) // передаём ошибку
}
}, 300)
})loadUser
.then(user => {
console.log('Пользователь:', user.name)
return user.id // возвращаем значение для следующего .then
})
.then(id => console.log('ID:', id)) // получаем то, что вернул предыдущий .then
.catch(e => console.error('Ошибка:', e.message)) // ловим любую ошибку из цепочки
.finally(() => console.log('Загрузка завершена')) // всегдаКлючевой момент: каждый .then() возвращает новый промис. Это позволяет строить цепочки.
// Вместо вложенных колбэков — плоская цепочка
getUser(1)
.then(user => getRepos(user.login)) // возвращаем промис
.then(repos => getCommits(repos[0].id)) // получаем его результат
.then(commits => render(commits))
.catch(e => showError(e.message))// Уже выполненный промис — удобно в тестах и дефолтных значениях
const p1 = Promise.resolve({ id: 1, name: 'Тест' })
p1.then(user => console.log(user.name)) // 'Тест'
// Уже отклонённый промис
const p2 = Promise.reject(new Error('Сервер недоступен'))
p2.catch(e => console.log(e.message)) // 'Сервер недоступен'Запускает несколько промисов одновременно и ждёт все:
// Вместо последовательных 300мс + 200мс + 150мс = 650мс...
// Параллельно: max(300, 200, 150) = 300мс
const [user, repos, notifications] = await Promise.all([
fetchUser(id), // 300мс
fetchRepos(id), // 200мс
fetchNotifications(id), // 150мс
])
// Всё пришло через 300мс!Если хотя бы один промис отклоняется — весь Promise.all отклоняется немедленно.
1. Не возвращают промис из .then():
// Сломано — вложенный промис, а не цепочка:
getUser(1)
.then(user => {
getRepos(user.id) // нет return — следующий .then не получит repos!
})
.then(repos => console.log(repos)) // repos === undefined
// Исправлено:
getUser(1)
.then(user => getRepos(user.id)) // return обязателен
.then(repos => console.log(repos.length))2. Забывают .catch():
// Сломано — ошибка "улетит" без обработки (UnhandledPromiseRejection):
getUser(1)
.then(user => render(user))
// нет .catch — если getUser упадёт, узнаем только из логов Node.js
// Исправлено:
getUser(1)
.then(user => render(user))
.catch(e => showErrorMessage(e.message))3. Создают Promise там, где он уже есть:
// Сломано — лишняя обёртка:
function getUser(id) {
return new Promise((resolve, reject) => {
fetch(`/api/users/${id}`)
.then(r => r.json())
.then(resolve)
.catch(reject)
})
}
// Исправлено — fetch уже возвращает промис:
function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json())
}useEffect с промисами для загрузки данныхAPI GitHub: цепочка промисов и параллельная загрузка данных
// Симуляция GitHub API
function fetchUser(login) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!login) return reject(new Error('Login обязателен'))
resolve({ login, name: 'Иван Петров', publicRepos: 42, followers: 318 })
}, 100)
})
}
function fetchRepos(login) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ name: 'awesome-project', stars: 124, lang: 'JavaScript' },
{ name: 'my-blog', stars: 18, lang: 'TypeScript' },
{ name: 'utils-lib', stars: 67, lang: 'JavaScript' },
])
}, 150)
})
}
function fetchFollowers(login) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ login: 'anna', name: 'Анна Смирнова' },
{ login: 'boris', name: 'Борис Иванов' },
])
}, 80)
})
}
// Цепочка: получаем пользователя, потом его репозитории
console.log('Загружаем профиль...')
fetchUser('ivanov')
.then(user => {
console.log(`${user.name}: ${user.publicRepos} репозиториев`)
return fetchRepos(user.login) // возвращаем промис — цепочка продолжается
})
.then(repos => {
const topRepo = repos.reduce((top, r) => r.stars > top.stars ? r : top)
console.log(`Топ репо: ${topRepo.name} (${topRepo.stars} ⭐)`)
return topRepo
})
.then(repo => console.log(`Язык: ${repo.lang}`))
.catch(e => console.error('Ошибка:', e.message))
.finally(() => console.log('Загрузка завершена'))
// Promise.all — параллельная загрузка репозиториев и подписчиков
Promise.all([
fetchRepos('ivanov'),
fetchFollowers('ivanov'),
])
.then(([repos, followers]) => {
console.log('Репозиториев:', repos.length) // 3
console.log('Подписчиков:', followers.length) // 2
})Ты разрабатываешь клиент для API задачника (To-do app). Реализуй функцию `delay(ms)` — возвращает промис, который разрешается через `ms` миллисекунд. Реализуй функцию `simulateApi(endpoint)` — возвращает промис, который: - Через 100мс resolves с `{ data: endpoint, timestamp: Date.now() }` - Если `endpoint` содержит строку `"error"` — rejects с `new Error('API Error: ' + endpoint)` Используй цепочку `.then/.catch` для обработки результатов.
delay: setTimeout(resolve, ms) — передаём resolve напрямую. simulateApi: если endpoint.includes("error") вызови reject(new Error(...)), иначе resolve({ data: endpoint, timestamp: Date.now() }). В цепочке — обязательно return Promise внутри .then.