JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Promises: создание, then, catch, finally

Promises: создание, then, catch, finally

Модуль 4: Node.js, Next.js и Angular
2 уровень , 2 лекция
Открыта

1. Проблема коллбеков и зачем нужны Promises

Давайте вспомним, как выглядел типичный асинхронный код на коллбеках:

const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Ошибка чтения файла:', err);
    return;
  }
  console.log('Содержимое файла:', data);
});

Всё хорошо, пока у нас одна операция. Но стоит добавить вторую, третью, сделать обработку ошибок — и вот уже ваш код напоминает рождественскую гирлянду из вложенных функций.

Callback hell — это когда уровень вложенности и количество кода растёт экспоненциально, а отладка становится похожа на разгадывание кроссворда без подсказок.

Promises пришли на смену коллбекам, чтобы сделать асинхронный код более читаемым, предсказуемым и удобным для обработки ошибок.

Что такое Promise

Promise (обещание) — это специальный объект, который представляет результат асинхронной операции, которая может завершиться успешно (fulfilled) или с ошибкой (rejected).

Представьте, что вы заказали пиццу. Вам обещают привезти её через 30 минут. Вы не знаете, привезут ли её вовремя, но у вас есть обещание (promise). Когда курьер приедет, вы либо получите пиццу (успех), либо останетесь голодным (ошибка).

Состояния Promise

У Promise есть три состояния:

Состояние Описание
pending Ожидание (обещание ещё не выполнено)
fulfilled Выполнено успешно (обещание сдержано)
rejected Ошибка (обещание не сдержано)

2. Создание Promise

Синтаксис создания Promise очень прост:

const myPromise = new Promise((resolve, reject) => {
  // Здесь выполняется асинхронная операция
  // Если всё хорошо:
  // resolve(результат);

  // Если что-то пошло не так:
  // reject(ошибка);
});

Пример: Promise, который выполняется успешно

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Готово!'); // Через 1 секунду Promise выполнится успешно
  }, 1000);
});

console.log('Обещание создано!');

Пример: Promise с ошибкой

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('Что-то пошло не так');
  }, 1000);
});

3. then: обработка успешного результата

Чтобы получить результат выполнения Promise, используется метод then:

promise.then((result) => {
  console.log('Результат:', result);
});

then принимает функцию, которая будет вызвана, когда Promise выполнится успешно (fulfilled).

Пример:

const promise = new Promise((resolve) => {
  setTimeout(() => resolve('Пицца доставлена!'), 1500);
});

promise.then((result) => {
  console.log(result); // "Пицца доставлена!"
});

4. catch: обработка ошибок

Для обработки ошибок используется метод catch. Он срабатывает, если Promise был отклонён (rejected):

promise.catch((error) => {
  console.error('Ошибка:', error);
});

Пример:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => reject('Пиццу не привезли :('), 1500);
});

promise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error); // "Пиццу не привезли :("
  });

5. finally: всегда выполняется

Иногда нужно выполнить какое-то действие всегда — независимо от того, успешно завершился Promise или с ошибкой (например, убрать спиннер загрузки). Для этого есть метод finally:

promise
  .then((result) => {
    console.log('Успех:', result);
  })
  .catch((error) => {
    console.error('Ошибка:', error);
  })
  .finally(() => {
    console.log('Завершено!'); // Всегда выполняется
  });

6. Цепочки Promises: магия then

Одна из главных фишек Promises — возможность строить цепочки из then:

new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000);
})
  .then((num) => {
    console.log('Первый then:', num); // 2
    return num * 2;
  })
  .then((num) => {
    console.log('Второй then:', num); // 4
    return num * 2;
  })
  .then((num) => {
    console.log('Третий then:', num); // 8
  });

Каждый then возвращает новый Promise, и результат передаётся дальше по цепочке.

Пример с ошибкой в цепочке

Если в любом then произойдёт ошибка (например, выбросится исключение или вернётся rejected Promise), управление перейдёт в ближайший catch:

new Promise((resolve) => {
  resolve('Начало');
})
  .then((msg) => {
    throw new Error('Ошибка в then!');
  })
  .catch((err) => {
    console.error('Поймали ошибку:', err.message);
  });

7. Практика: Promise для чтения файла

Давайте попробуем использовать Promise для асинхронного чтения файла с помощью модуля fs.promises:

const fs = require('fs/promises');

fs.readFile('file.txt', 'utf8')
  .then((data) => {
    console.log('Содержимое файла:', data);
  })
  .catch((err) => {
    console.error('Ошибка чтения файла:', err);
  })
  .finally(() => {
    console.log('Операция завершена');
  });

Теперь вы видите, насколько проще и чище выглядит код по сравнению с callback-стилем. Ошибки и успехи обрабатываются отдельно, вложенности нет, а цепочки then позволяют элегантно строить последовательные асинхронные операции.

8. Развиваем наше приложение: асинхронная цепочка

Допустим, мы делаем мини-приложение, которое:

  1. Читает файл с задачами (tasks.json)
  2. Добавляет новую задачу
  3. Записывает результат обратно в файл

Вот как это может выглядеть с Promises:

const fs = require('fs/promises');

function addTask(task) {
  return fs.readFile('tasks.json', 'utf8')
    .then((data) => {
      const tasks = JSON.parse(data);
      tasks.push(task);
      return fs.writeFile('tasks.json', JSON.stringify(tasks, null, 2));
    })
    .then(() => {
      console.log('Задача добавлена!');
    })
    .catch((err) => {
      console.error('Ошибка:', err);
    });
}

addTask({ title: 'Изучить Promises', done: false });

Здесь мы используем цепочку then: сначала читаем файл, затем парсим JSON, добавляем задачу, записываем обратно — и всё это без коллбекового ада!

9. Типичные ошибки при работе с Promises

Ошибка №1: забыли вернуть Promise из then

Если внутри then вы делаете асинхронную операцию, обязательно возвращайте Promise! Иначе цепочка не дождётся её завершения.

// Неправильно
.then(() => {
  fs.writeFile('file.txt', 'Текст'); // забыли return!
})

// Правильно
.then(() => {
  return fs.writeFile('file.txt', 'Текст');
})

Ошибка №2: не обработали ошибку

Если не добавить catch, ошибка может "потеряться", и приложение завершится с неожиданной ошибкой. Всегда добавляйте catch в конце цепочки.

Ошибка №3: попытка использовать результат вне then

Promise — это асинхронность! Всё, что зависит от результата, должно быть внутри then/catch, иначе получите undefined.

let data;
fs.readFile('file.txt', 'utf8').then((result) => {
  data = result;
});
console.log(data); // undefined, потому что Promise ещё не завершён!

Ошибка №4: забыли finally для очистки ресурсов

Если у вас есть ресурсы, которые нужно обязательно закрыть/освободить, используйте finally — иначе при ошибках cleanup не произойдёт.

Ошибка №5: вложенность then вместо цепочки

Не пишите так:

fs.readFile('file.txt', 'utf8').then((data) => {
  fs.writeFile('copy.txt', data).then(() => {
    console.log('Готово');
  });
});

Лучше строить цепочку:

fs.readFile('file.txt', 'utf8')
  .then((data) => fs.writeFile('copy.txt', data))
  .then(() => console.log('Готово'));
1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 2 лекция
Недоступна
Обработка ошибок в Promise
Обработка ошибок в Promise
1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 2 лекция
Недоступна
Использование then, catch и finally в цепочке Promises
Использование then, catch и finally в цепочке Promises
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ