Эта статья содержит простое объяснение, как работать с промисами, с краткими примерами кода. Если вы уже использовали промисы в JavaScript, вряд ли вы узнаете что-то новое — прим. перев.


JavaScript — это синхронный язык программирования, но благодаря колбэкам мы можем заставить его работать асинхронно. Вместе с колбэками приходит «Ад обратных вызовов». Промисы изобрели, чтобы писать асинхронные программы без него.


Промисы


Промисы (от английского Promise — обещание) очень похожи на обещания из реальной жизни.



  • После того, как обещание сделано, мы получаем обязательство выполнения «чего-то» в будущем и можем строить дальнейшие планы, исходя из этого
  • Как и в реальной жизни, обещания могут быть сдержаны или нарушены
  • Обещания не готовы мгновенно, мы можем работать с их результатами только после того, как обещания сдержаны

В JavaScript: «Объект Promise (промис) используется для отложенных и асинхронных вычислений».


Создание промисов


Создание объектов происходит по такому шаблону:


new Promise(function(resolve, reject) { ... });

Выполняющая функция (executor) принимает два аргумента resolve и reject, которые являются колбэками. Промисы используются для обработки асинхронных операций, являющихся блокирующим кодом. Обращения к БД, I/O или API — всё это примеры таких операций, с которыми должна разобраться выполняющая функция. Как только она завершится, она либо исполнит промис (вызовет resolve), либо отклонит его (вызовет reject).


Простой пример:


let promise = new Promise(function(resolve, reject) {

if(promise_kept) {
  resolve("done");
} else {
  reject(new Error("…"));  
}

});

Как видно, Promise не возвращает значение сразу. Он ждёт завершения блокирующей операции и вызывает подходящий колбэк. Это позволяет асинхронным методам возвращать значения, как если бы они были синхронными. Просто вместо возвращения значения (которого пока нет) возвращается промис.


Очевидно, что у промиса могут быть разные состояния:


  1. ожидание (pending): начальное состояние, не исполнен и не отклонен
  2. исполнено (fulfilled): операция завершена успешно
  3. отклонено (rejected): операция завершена с ошибкой

Пример:



Использование промисов


С созданием промисов проблем быть не должно, так что давайте перейдём к их использованию:


const isDone = new Promise()
//...

const checkIfDone = () => {
  isDone
    .then(ok => {
      console.log(ok)
    })
    .catch(err => {
      console.error(error)
    })
}

Вызов .checkIfDone() начнёт исполнение промиса isDone и дождётся его завершения, вызвав колбэк для результата. В случае ошибки будет вызван метод .catch().


Цепочка промисов


Методы промиса .then() и .catch() сами возвращают промис, для которого можно вновь вызвать .then() или .catch(), создав таким образом цепочку из промисов:


new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); 

}).then(function(result) { 

  alert(result); 
  return result * 3;

}).then(function(result) { 

  alert(result); 
  return result * 4;

}).then(function(result) {

  alert(result); 
  return result * 6;

});

В коде выше каждый промис передаёт результат своего исполнения в следующий .then() и так до полного завершения всей цепочки.


Заключение


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

Комментарии (9)


  1. vvadzim
    16.12.2019 15:20
    +2

    1. germn Автор
      16.12.2019 15:34
      -1

      Я понимаю, конечно, что статьи о промисах уже были. Конкретно эта мне понравилась своей краткостью и простотой: она не пытается углубляться в детали, а даёт базовые знания за минимальное время.

      Для тех, кому это неинтересно, я специально добавил дисклеймер в самом начале.


      1. vvadzim
        16.12.2019 15:55
        +1

        Возможно, я чего-то не понимаю. Но я правда не понимаю причины такой популярности темы промисов в сравнении со, скажем, темой циклов.
        while, do-while, for, for-in, for-of, for-await-of — неужели это проще метода then?


        1. GCU
          16.12.2019 16:57
          +1

          Скажу про себя — промисы — это какой-то тёмный дремучий и непонятный асинхронный лес для меня.
          Это чем-то напоминает известный мем — как нарисовать сову.

          Смотрите вот промис, вот мы сделали цепочку — всё классно работает — и главное выглядит как чисто/приятно без ужасов, что может быть проще!

          А потом оказывается что на практике это вообще не цепочка, на один и тот же промис пачками навешаны then, catch и finally, промисы порождают по несколько других промисов, какие-то ждут всех, какие-то первого попавшегося и это всё надо держать в голове, чтобы понять что может пойти не так и как это должно работать в случае ошибки (которая ещё и не всегда воспроизводится). И ты обкладываешь всё это логами, пытаясь восстановить последовательность действий, каждый раз получаешь новый частный случай последовательности и надеешься что при других раскладах (которые ты предусмотрел, но не смог проверить на практике) всё отработает правильно.

          Коллбеки были неудобные и наворачивать на них что-то сложное было проблематично. На события можно было как поцепить обработчик, так и отцепить. И вот появились синтаксически сахарные промисы, на которых вообще непонятно как отцепить/перецепить обработчики и остановить этот асинхронный паровоз.

          Я рад за людей у которых все просто и прекрасно работает, трава зелёная и солнце светит. Люди прекрасно работают с промисами (это же стандарт), я читаю каждую статью о промисах в надежде отловить ту самую, которая позволит мне с радостью, а не со страхом смотреть на асинхронные промис паровозы.


          1. DarthWazer
            16.12.2019 17:10

            Хм… Такое чувство, как будто Вы не любите JavaScript! >.<


            1. GCU
              16.12.2019 17:36

              Возможность «вывернуть» задачу из последовательности действий в некий аналог графа зависимостей (типа makefile) это прикольно и чертовски приятно когда слаженно работает в разных последовательностях, но как в анекдоте про помидоры:
              — Гоги, ты помидоры любишь?
              — Кушать — да, а так не очень…


          1. Aingis
            16.12.2019 19:23

            Хром девтулзы умеют переходить вперёд по цепочке по команде Step into. Но стектрейс не посмотреть, это неудобно, да…


          1. vvadzim
            17.12.2019 20:59

            на которых вообще непонятно как отцепить/перецепить обработчики и остановить этот асинхронный паровоз.
            Никак. Подцепить обработчик можно. Отцепить — нет. Сам страдаю.

            Но вообще вы всё правильно поняли. Промис — объект с двумя событиями. Одно называется условно onResolve, другое onReject. Обработчиков сколько угодно можно нацеплять через метод then. Событие бросится только одно. Каждый обработчик этого события вызовется только один раз. Даже если был подцеплен после того, как некоторые другие обработчики были уже вызваны. Т.е. пропустить событие невозможно. В приципе всё.

            Остальное уже детали. Другие методы в промисах просто реализуют типичные паттерны использования. Ну чтоб меньше спагетти-писанины.


            1. vvadzim
              17.12.2019 21:13

              А, ну, значение, возвращённое из обработчика события, будет использовано для создания нового экземпляра класса Promise с этим значением в качестве события. Теперь точно всё :)