Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Promises

Промисы стали настоящим спасением человечества и среди прогрессивных разработчиков являются основным способом управления асинхронным кодом.

Полное описание всех возможностей и аспектов поведения промисов является объемной задачей, которая может запутать на первых порах, поэтому в этом уроке мы остановимся на ключевых особенностях поведения. Все остальное можно почерпнуть из стандарта и/или документации.

Знакомству с промисами способствует понимание темы "конечные автоматы".

Начнем по традиции с примера:

const file = '/tmp/hello1.txt';
import { writeFile, readFile } from 'fs-promise';

writeFile(file, 'hello world')
  .then(() => readFile(file, 'utf8'))
  .then(contents => console.log(contents))
  .catch(err => console.log(err));
// hello world

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

Абзац выше – это пример того, как выглядит типичная программа, построенная на промисах. Так что такое промис?

Объект, используемый для асинхронных операций. Промис содержит в себе результат выполнения и позволяет строить цепочки из вычислений, избегая проблемы callback hell

Интерфейс:

  • Promise.prototype.then(onFulfilled, onRejected)
  • Promise.prototype.catch(onRejected)

Отсутствие callback hell происходит благодаря тому, что мы всегда работаем на уровне последовательных вызовов then, а не уходим в глубину.

Разберем пример выше по косточкам. Первый вызов writeFile(file, 'hello world') возвращает тот самый промис, и пока не важно, как он строится внутри, сейчас мы пытаемся понять то, как с ним работать.

// Вызов ничем не отличается кроме того, что мы не передаем колбек
writeFile(file, 'hello world')

После этого у нас есть два варианта:

  • Мы вызываем then и передаем функцию onFulfilled, которая будет вызвана в случае успешного выполнения асинхронной операции
  • Мы вызываем catch и передаем функцию onRejected, которая будет вызвана, в случае ошибок в результате выполнения асинхронной операции.

Функция onFulfilled принимает на вход данные, которые были получены в результате предыдущего выполнения. Таким образом идет передача данных по цепочке.

.then(() => readFile(file, 'utf8'))
.then(contents => console.log(contents))

Данные, возвращаемые из функции onFulfilled, переходят по цепочке в функцию onFulfilled следующего then. Но если вернуть promise, то в следующем then окажутся данные, полученные в результате выполнения этого промиса, а не сам промис. Что и происходит в примере выше: мы возвращаем readFile(), а ниже получаем contents. То есть, промисы хорошо комбинируются друг с другом.

Конечный автомат

Теперь попробуем посмотреть внутрь промиса. С концептуальной точки зрения промис – это конечный автомат, у которого три состояния: pending, fulfilled, rejected.

Promise states

Изначально он находится в состоянии pending, а дальше может перейти в одно из двух: либо выполнен (fulfilled), либо отклонен (rejected). И все, больше никакие переходы невозможны. Придя один раз в одно из терминальных (конечных) состояний, промис больше не подвержен изменениям, как бы мы не старались снаружи заставить его перейти в другое состояние.

Реализация

const promiseReadFile = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
      err ? reject(err) : resolve(data);
    });
  });
};

Любая функция возвращающая промис, внутри себя создает объект промиса привычным способом. Конструктор Promise принимает на вход функцию, внутри которой запускается выполнение асинхронной операции. Делается это, кстати, сразу, промисы не являются примером отложенного (lazy) выполнения кода. Но это еще не все. Промис требует от нас некоторых действий для своей работы. Во входную функцию передаются две другие: reject и resolve. reject должна быть вызвана в случае ошибки с передачей внутрь объекта error, а resolve — в случае успешного завершения асинхронной операции с передачей внутрь данных, если они есть.

Ошибки

Ошибка обрабатывается ближайшим обработчиком onRejected в цепочке вызовов. При этом существует два варианта определения обработчика. Первый - через catch, второй - с помощью передачи в then второго параметра. Это продемонстрировано в примере ниже:


promiseReadFile('file1')
  .then(data => promiseWriteFile('file2', data))
  .then(() => promiseReadFile('file3'))
  .then(data => console.log(data))
  .catch(err => console.log(err));
  // .then(null, err => console.log(err));

Promise.all

Иногда возникает необходимость дождаться выполнения нескольких асинхронных операций. В этом случае можно воспользоваться идиомой Promise.all. Работает она очень просто: в эту функцию передается массив промисов, а дальше в then приходит массив с результатами выполнения.

const readJsonFiles = filenames => {
  // N.B. passing readJSON as a function,
  // not calling it with `()`
  return Promise.all(filenames.map(readJSON));
}

readJsonFiles(['a.json', 'b.json'])
  .then(results => {
    // results is an array of the values
    // stored in a.json and b.json
  });

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».