1. Введение
Давайте разберём основные плюсы Promises на конкретных примерах и сравнениях.
Читаемость и линейность кода
С callback-ами код быстро превращается в "пирамиду ужаса" — вложенность растёт, а вместе с ней и путаница.
С Promises вы пишете код "слева направо", шаг за шагом:
getUser()
.then(user => getPosts(user))
.then(posts => showPosts(posts))
.catch(err => showError(err));
Это легко читать, поддерживать и объяснить коллеге (или самому себе через месяц).
Централизованная обработка ошибок
В callback-ах приходится обрабатывать ошибку на каждом уровне:
asyncOperation(function(err, result) {
if (err) {
// обработка ошибки
return;
}
// ...
});
С Promises можно обработать все ошибки в одном месте с помощью .catch():
doA()
.then(doB)
.then(doC)
.catch(err => {
// обработка всех ошибок
});
Это особенно ценно, если операций много — не нужно дублировать обработку ошибок.
Возврат значений и цепочки
В callback-ах нельзя использовать return, чтобы вернуть результат асинхронной операции наружу — только внутри callback-а.
С Promises можно возвращать значения, и они "переходят" в следующий then:
function getNumber() {
return new Promise(resolve => {
setTimeout(() => resolve(42), 1000);
});
}
getNumber()
.then(number => number * 2) // 84
.then(result => console.log(result)); // 84
Избежание "callback hell"
Самое важное: Promises позволяют избавиться от вложенности, которая со временем превращается в головную боль.
Callback hell:
a(function(err, resA) {
b(resA, function(err, resB) {
c(resB, function(err, resC) {
// ...
});
});
});
Promise chain:
a()
.then(b)
.then(c)
.then(resC => {
// ...
})
.catch(err => {
// обработка ошибки на любом этапе
});
Комбинирование асинхронных операций
С Promises легко запускать несколько операций параллельно и ждать их завершения:
Promise.all([
fs.readFile('a.txt', 'utf8'),
fs.readFile('b.txt', 'utf8')
])
.then(([a, b]) => {
console.log('Оба файла прочитаны:', a, b);
})
.catch(err => {
console.error('Ошибка при чтении файлов:', err);
});
В callback-ах для этого пришлось бы городить "счётчики", вручную отслеживать завершение каждой операции.
Поддержка синтаксиса async/await
Хотя это тема следующей лекции, важно знать: Promises открывают путь к ещё более удобному синтаксису — async/await. Он позволяет писать асинхронный код почти как синхронный, но это уже совсем другая история...
2. Примеры: практикуемся с Promises
Пример 1: Чтение и запись файлов с Promises
Давайте возьмём пример из callback-версии и перепишем его с использованием Promises:
Callback-версия:
const fs = require('fs');
fs.readFile('input.txt', 'utf8', function(err, data) {
if (err) {
console.error('Ошибка чтения:', err);
return;
}
const result = data.toUpperCase();
fs.writeFile('output.txt', result, function(err) {
if (err) {
console.error('Ошибка записи:', err);
return;
}
console.log('Файл успешно записан');
});
});
Promise-версия:
const fs = require('fs').promises;
fs.readFile('input.txt', 'utf8')
.then(data => data.toUpperCase())
.then(result => fs.writeFile('output.txt', result))
.then(() => console.log('Файл успешно записан'))
.catch(err => console.error('Ошибка:', err));
Пример 2: Цепочка асинхронных действий
Допустим, у нас есть три асинхронные операции: получить пользователя, получить его заказы, получить детали первого заказа.
Callback-hell:
getUser(function(err, user) {
if (err) return handleError(err);
getOrders(user.id, function(err, orders) {
if (err) return handleError(err);
getOrderDetails(orders[0], function(err, details) {
if (err) return handleError(err);
showOrderDetails(details);
});
});
});
Promise-chain:
getUser()
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0]))
.then(details => showOrderDetails(details))
.catch(handleError);
Пример 3: Параллельное выполнение нескольких операций
С Promises можно легко запускать несколько задач одновременно и ждать их завершения:
Promise.all([loadUser(), loadSettings()])
.then(([user, settings]) => {
console.log('Пользователь и настройки загружены:', user, settings);
})
.catch(err => {
console.error('Ошибка при загрузке данных:', err);
});
В callback-ах для этого пришлось бы изобретать велосипед с флагами и счётчиками.
3. Типичные ошибки при переходе с callback-ов на Promises
Ошибка №1: забыли вернуть Promise из then
Если в цепочке then не вернуть Promise, следующий then выполнится сразу, а не после завершения асинхронной операции. Например:
// Неправильно:
doSomething()
.then(result => {
asyncOp(result); // забыли return!
})
.then(...); // выполнится сразу, а не после asyncOp
// Правильно:
doSomething()
.then(result => {
return asyncOp(result); // возвращаем Promise!
})
.then(...);
Ошибка №2: обработка ошибок только в одном then
Иногда забывают поставить catch в конце цепочки. Если ошибка произойдёт, она "потеряется". Всегда завершайте цепочку catch-ом!
Ошибка №3: смешивание callback-ов и Promises
Иногда используют старые функции с callback-ами внутри then — это приводит к путанице. Если возможно, используйте либо чистые Promises, либо "обёртывайте" callback-функции с помощью util.promisify().
Ошибка №4: забыли обработать ошибку в Promise
Если не обработать ошибку (catch), Node.js может выдать предупреждение об UnhandledPromiseRejectionWarning. Не забывайте ловить ошибки!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ