Если в проекте появились промисы, то, по хорошему, весь код должен работать только через них. К сожалению, далеко не все библиотеки имеют интерфейс с промисами и работают, по старинке, на колбеках. В таких случаях нужно "обернуть" или как говорят "промисифицировать". Создание промиса происходит с помощью конструктора Promise:

import fs from 'fs';

const promise = new Promise((resolve, reject) => {
  fs.readFile('/etc/passwd', (err, data) => {
    if (err) {
      reject(err);
    }
    resolve(data);
  });
})

Промис ожидает на вход функцию, которая будет вызвана в момент создания. Именно внутри этой функции и нужно выполнять асинхронную операцию (на колбеках), которую мы хотим превратить в промис. Промис прокидывает в эту функцию два колбека:

  • resolve - должна быть вызвана в случае успешного завершения асинхронной операции. Ей на вход отдается результат этой операции.
  • reject - должна быть вызвана в случае ошибки. На вход, соответственно, отдается ошибка.

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

В конечном итоге конструкция new Promise возвращает самый настоящий промис, с которым можно работать уже привычным для нас способом:

promise
  .then(console.log)
  .catch(console.log)

А что если нужно обернуть две асинхронных операции или три или даже больше? Придется оборачивать каждую из них независимо, другими словами одна асинхронная операция - один конструктор new Promise. Кстати, эту задачу можно автоматизировать, и в ноду встроена специальная функция, которая делает промисы из асинхронных функций:

import util from 'util';
import fs from 'fs';

const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
});

Во фронтенде такое тоже возможно, достаточно "загуглить" пакет, предоставляющий функцию promisify.

Устройство

Состояния промиса

С технической точки зрения, промис это объект имеющий три состояния (см. конечные автоматы и автоматное программированмие): pending, resolved и rejected. Промис начинается в состоянии pending, а затем, с помощью функций (событий как говорят в теории автоматов) resolve и reject переводится в одно из конечных (терминальных) состояний resolved или rejected. Однажды перейдя в эти состояния, промис уже не может откатиться назад или уйти в другое терминальное состояние. То есть после вызова resolve, нет способа привести промис в состояние rejected вызывая функцию reject.


Дополнительные материалы

  1. util.promisify