Перевод первой части отличной статьи про промисы. Базовые приемы создания и управления промисами.


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



Если есть другие операции, выполнение которых зависит от этого сетевого запроса, то вырисовывается проблема. Без промисов нам придётся использовать вереницу колбэков(callbacks), чтобы выстроить последовательность операций. Это нормально, если у нас одно асинхронное действие. Но если нужно сделать несколько последовательных асинхронных шагов, колбэки становятся неуправляемыми и результат печально известен как лапша колбеков (callback hell)


doSomething(function(responseOne) {  
    doSomethingElse(responseOne, function(responseTwo, err) {
        if (err) { handleError(err); }
        doMoreStuff(responseTwo, function(responseThree, err) {
            if (err) { handleAnotherError(err); }
            doFinalThing(responseThree, function(err) {
                if (err) { handleAnotherError(err); }
                // Выполнено
            }); // конец doFinalThing
        }); // конец doMoreStuff
    }); // конец doSomethingElse
}); // конец doSomething

Промисы предоставляют стандартизированный и понятный метод решения задач, которые должны выполняться последовательно.


doSomething()  
  .then(doSomethingElse)
  .catch(handleError)
  .then(doMoreStuff)
  .then(doFinalThing)
  .catch(handleAnotherError)

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


Промисы создаются при помощи конструктора промисов. Он представляет собой функцию с двумя аргументами (resolve & reject) в качесте параметров.


var promise = new Promise(function(resolve, reject) { /* Содержимое промиса */ } ) 

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


Вот промис версия XMLHttpRequest -


/* CREDIT - Jake Archibald, http://www.html5rocks.com/en/tutorials/es6/promises/ */

function get(url) {  
  return new Promise(function(resolve, reject) {

    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      if (req.status == 200) { 
          resolve(req.response); /* ПРОМИС ВЫПОЛНЕН */
      } else { 
          reject(Error(req.statusText)); /* ПРОМИС ОТКЛОНЁН */
      }
    };

    req.onerror = function() { reject(Error("Network Error")); };
    req.send();
  });
}

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


Что бы выполнить промис, мы можем вызвать его как любую обычную функцию. Но, так как это промис, у нас есть доступ к .then методу, который мы можем добавить к функции и который будет исполнен когда промис выйдет из режима ожидания.


.then() метод принимает два необязательных параметра. Первый — это функция, которая вызывается, когда промис исполнен(resolved). Второй — функция, которая выполняется если промис отклонён(rejected).


get(url)  
.then(function(response) {
    /* successFunction */
}, function(err) {
    /* errorFunction */
})

.then


Обработка ошибок


Так как оба параметра (successFunction и errorFunction) опциональны, мы можем разделить их на два .then() для лучшей читаемости.


get(url)  
.then(function(response) {
    /* successFunction */
}, undefined)
.then(undefined, function(err) {
    /* errorFunction */
})

Что бы сделать код еще более понятным, мы можем использовать .catch() метод, который является сокращенным вариантом для .then(undefined, errorFunction)


get(url)  
.then(function(response) {
    /* successFunction */
})
.catch(function(err) {
    /* errorFunction */
})

.catch


Формирование цепи


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


Мы можем сделать это, возвращая еще один промис после выполнения или отклонения предыдущего. Например -


get(url)  
.then(function(response) {
    response = JSON.parse(response);
    var secondURL = response.data.url
    return get( secondURL ); /* Возвращаем новый промис */
})
.then(function(response) {
    response = JSON.parse(response);
    var thirdURL = response.data.url
    return get( thirdURL ); /* Возвращаем новый промис */
})
.catch(function(err) {
    handleError(err);
});

Если промис исполнен(resolved), то вызовется ближайший .then() в последовательности. Если промис отклонён(rejected), то ближайший .catch() в последовательности.
chaining


Паралелльное выполнение промисов


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


Чтобы это сделать, нам необходимо использовать два метода. Это Array.map(), для того, что бы применить промис для каждого элемента массива и сохранить результат в новый массив. И Promise.all(), который выполнит resolve() в случае исполнения всех промисов в массиве. Если хоть один промис в массиве будет отклонен, Promise.all() тоже будет отклонён.


var arrayOfURLs = ['one.json', 'two.json', 'three.json', 'four.json'];  
var arrayOfPromises = arrayOfURLs.map(get);

Promise.all(arrayOfPromises)  
.then(function(arrayOfResults) {
    /* Сделать что-нибудь, когда все промисы в массиве зарезолвятся */
})
.catch(function(err) {
    /* Выполняется, если хоть один промис в массиве отклонён */
})

Promise.all
Если мы посмотрим в сетевую панель (Network panel) инструментов разработки (Development tools), мы увидим, что все запросы случаются параллельно.


Development Tools


Если вам нужна поддержка IE и/или Opera Mini, используйте полифил.
Спасибо за внимание!

Поделиться с друзьями
-->

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


  1. n0wheremany
    14.10.2016 12:52
    +1

    Недавно уже было. Но на мой взгляд эта статья более понятна, без всякой лишней воды.


    1. Stan_1
      17.10.2016 08:46

      Да, буквально неделю назад разбирался с промисами, и потерял два дня. :( Толковых материалов мало, в итоге доходил своим умом. Эта статья — очень толкова. Жалко, ее не было две недели назад.


  1. vlreshet
    14.10.2016 12:57

    Объясните идиоту (мне) — промисы только делают синтаксис в синхронном стиле, функции же остаются асинхронными? То есть, если, допустим, в nodejs сервере, использовать промисы на все асинхронные операции — то весь потом не будет блокироваться при этом? Промисы — просто удобная обёртка?


    1. ArtemNIkolaev
      14.10.2016 13:13
      +1

      1. Да, функции остаются асинхронными.
      2. Да, поток не будет блокироваться.
      3. И да и нет. В простейших ситуациях промисы действительно больше похожи на удобную обёртку. Однако уже .then() и .catch() дают нам мощный инструмент для управления последовательностью выполнения. А кроме этого есть еще Promise.all() и Promise.race(). Эти методы подробно рассматриваются во второй статье Promises 102(ссылка на оригинал, статья будет переведена в течение ближайшей недели).


      1. vlreshet
        14.10.2016 13:40

        Спасибо! Теперь осталось понять нужны ли мне они, ведь по сути — что в коллбек надо функцию передавать, что в then надо функцию передавать. Те же яйца только в профиль.


        1. inook
          14.10.2016 13:52

          Почитай про async/await функции. В 7 версии node.js стандартные библиотеки promisify будут и уже async/await работает с флагом harmony.


        1. bromzh
          14.10.2016 13:56

          Те же яйца только в профиль.

          Нет. Функция, переданная в then или catch может что-то вернуть или возбудить исключение, т.е. можно использовать return и throw как в обычных функциях. Если функция завершилась нормально, то её результат попадает в следующий then, если было исключение — то exception попадёт в следующий catch. С обычными колбеками так не получится.


        1. ArtemNIkolaev
          14.10.2016 13:57
          +1

          Тут я с вами не соглашусь. Допустим, вам предлагают на выбор один из двух проектов:


          Пример кода из первого проекта
          doSomething(function(responseOne) {  
              doSomethingElse(responseOne, function(responseTwo, err) {
                  if (err) { handleError(err); }
                  doMoreStuff(responseTwo, function(responseThree, err) {
                      if (err) { handleAnotherError(err); }
                      doFinalThing(responseThree, function(err) {
                          if (err) { handleAnotherError(err); }
                          // Выполнено
                      }); // конец doFinalThing
                  }); // конец doMoreStuff
              }); // конец doSomethingElse
          }); // конец doSomething


          1. san9s
            15.10.2016 08:03
            +1

            Единственное принципиальное отличие первого от второго заключается в том, что в первом случае кто-то, для устрашения, написал анонимные функции, и выглядит оно страшно, а во втором, используя промисы — таки написал какие-то НЕ анонимные функции. А кажется, что разница на лицо.

            Однако, на самом деле, разницы не будет заметно до тех пор, пока не начнет работать async/await, позволяющий тут же вернуть значение из функции, а не обрабатывать его в следующей. Лапшичка (callback hell) никуда не делась, она просто перекрасилась в другой цвет.


            1. ArtemNIkolaev
              17.10.2016 11:59

              Вы всё-таки не поняли что я имел ввиду, позвольте уточнить. По другому первый пример можно представить вот так:


              Другая версия первого примера
              function doFinalThingCallback(err) {
                  if (err) { handleAnotherError(err); }
                  // Выполнено
              } // конец doFinalThing
              
              function doMoreStuffCallback(responseThree, err) {
                  if (err) { handleAnotherError(err); }
                  doFinalThing(responseThree, doFinalThingCallback);
              } // конец doMoreStuff
              
              function doSomethingElseCallback(responseTwo, err) {
                  if (err) { handleError(err); }
                  doMoreStuff(responseTwo, doMoreStuffCallback);
              } // конец doSomethingElse
              
              function doSomethingCallback(responseOne) {  
                  doSomethingElse(responseOne, doSomethingElseCallback);
              } // конец doSomething
              
              doSomething(doSomethingCallback); // конец doSomething


        1. Sayonji
          15.10.2016 02:32
          +1

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


    1. Dawnreader
      14.10.2016 13:14

      Да. Просто более читабельный вид. Потом был шаг к упрощению при помощи генераторов, а в следующем стандарте вероятно появится async\await.


    1. equicolor
      14.10.2016 13:14

      Все верно, ваш код будет выполняться асинхронно, так же, как при использовании кэлбэков. Промис скорее другой подход, нежели обертка: вы можете предоставить возможность передать кэлбэк, а можете вернуть промис из функции. Второе предпочтительнее, первый подход считается устаревшим. Ну а синхронный и промис стили все-таки не одно и тоже. Совсем «синхронно» это оператор await


      1. jMas
        14.10.2016 13:18

        Разъясните, await просто останавливает поток выполнения до получения результата? Другими словами, это способ сделать асинхронный код синхронным?


        1. ArtemNIkolaev
          14.10.2016 13:37
          +1

          Позвольте процитировать другую статью на хабре — Async/Await в javascript. Взгляд со стороны:


          Async/Await в javascript. Взгляд со стороны

          Говоря общедоступным языком async/await?—?это Promise.


          Когда вы объявляете функцию как асинхронную, через волшебное слово async, вы говорите, что данная функция возвращает Promise. Каждая вещь которую вы ожидаете внутри этой функции, используя волшебное слово await, то же возвращает Promise.


        1. Alternator
          14.10.2016 15:36

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

          И самое приятное, что любое исключение(хоть синхронное, хоть асинхронное) может быть одинаково поймано с помощью try..catch


    1. DarthVictor
      14.10.2016 13:15
      +1

      Да. Я Вам даже больше скажу, в известных мне реализациях JavaScript (NodeJS, любой современный браузер) не существует механизмов превращения асинхронного кода в синхронный, равно как не существует и обратного механизма. Существуют обертки разной степени сложности реализации (promises, async&await), но все они не меняют физического способа вызова функции и работы runtime'а.


      1. vintage
        15.10.2016 19:59

        В NodeJS существуют fibers.


    1. Kongo
      14.10.2016 14:14
      +2

      Promises, как шаблон проектирования, предназначен не только для избавления от стилистически некошерных «ёлок», появляющихся при оформлении асинхронных вызовов. Да, они действительно ужасны. Но с асинхронными вызовами появляется риск и посерьёзней, чем сложность восприятия вложенных блоков. И это инверсия контроля. Обещания, это инструмент, созданный для помощи в инвертировании этой инверсии.

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

      Вот обещания и предоставляют стандартный паттерн по решению этих задач.


    1. Envek
      14.10.2016 23:50
      +1

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


  1. aeiuo
    14.10.2016 14:38
    -1

    Как по мне, то промисы не сильно лучше чем тот же callback hell. Вот await — другое дело. Интересно как с await-ами организовать такое:

    Может возникнуть ситуация, когда нам понадобится выполнить несколько промисов параллельно, и продолжать алгоритм только после того, как все промисы будут выполнены.

    Какой-нибудь особый параллельный for?


    1. ArtemNIkolaev
      14.10.2016 14:57

      Не согласен, промисы при любом раскладе сильно лучше колбеков. Дождитесь перевода второй статьи Promises 102, где подробно рассказано о том, как их готовить. Так же рекомендую вам ознакомиться со статьёй У нас проблемы с промисами. Автор рассматривает всевозможные проблемы, которые могут возникнуть и возникают при неправильном использовании промисов.
      По поводу вашего вопроса, я уже ссылался выше на статью Async/Await в javascript. Взгляд со стороны


      Конкретно на ваш вопрос отвечает следующая цитата:


      А если мне нужно одновременно получить результат от нескольких вызовов?

      Так как мы уже разобрались, что мы имеем дело с Promise. Следовательно можно использовать метод .all() объекта Promise для решения такого рода задач.


      async function unicorn() {
        let [rainbow, food] = await Promise.all([getRainbow(), getFood()]);
        return {rainbow, food}
      }


      1. aeiuo
        14.10.2016 16:46
        -2

        Более организованные коллбеки, но всё еще коллбеки.
        Вот это вот «Promise.» хорошо бы убрать отовсюду вообще и добавлять по мере надобности новые ключевые слова например fork/join(All)

        var a = fork getA(); // выполнение продолжается без ожидания
        var b = fork getB();
        join; // выполнение приостанавливается пока все fork в этом scope не завершатся
        


    1. Ohar
      14.10.2016 17:26

      1. aeiuo
        14.10.2016 17:43

        Я видел. Не нравится мне Promise, костыль это, возникший чтобы покрыть недостающие фичи языка.


        1. Ohar
          14.10.2016 17:53

          Очень интересное мнение.
          А как бы вы предложили реализовать работу с асинхронными операциями?


          1. aeiuo
            14.10.2016 18:56

            Уже есть async/await, осталось перестать вязать его с коллбеками и промисами, подогнать API на замену всяким XMLHttpRequest ну и добавить то, чего не хватает для сложных требований типа параллельно выполнить и подождать выполнения всех:

            примеры
            var valueFromAnySource = race AsyncHttp.get(url1);
            valueFromAnySource = race AsyncHttp.get(url2);
            finish;
            console.log(valueFromAnySource);
            
            var part1 = fork AsyncHttp.request(...);
            var part2 = fork AsyncHttp.request(...);
            join;
            console.log(part1 + part2)
            

            // если нужно соединить тексты из списка URL в изначальном порядке
            // решение без распараллеливания:
            var text = ''
            for (var url in urls) {
            	text += await AsyncHttp.get(url)
            }
            console.log(text);
            
            // решение с распараллеливанием:
            var texts = []
            var index = 0;
            for (var url in urls) {
            	texts[index++] = fork AsyncHttp.get(url)
            }
            join;
            console.log(texts.join(''));
            

            Еще пример:
            // в обычном мире:
            var p1 = new Promise(
            	function(resolve, reject) {
            		var xhr = new XMLHttpRequest();
            		xhr.open('GET', 'http://google.com');
            		xhr.send();
            		xhr.onload = function () {
            			resolve(this.response);
            		}
            		xhr.onerror = function() {
            			reject(this.statusText);
            		}
            	}
            );
            p1.then(
            	function(result) {
            		console.log(result);
            	})
            .catch(
            	function(err) {
            		console.log('error:' + err);
            	});
            

            // вымышленный мир без промисов и коллбеков:
            try {
            	var response = await AsyncHttp.request('GET', 'http://google.com');
            	console.log(response.statusCode);
            	console.log(response.content);
            } catch (err) {
            	console.log('error:' + err);
            }
            


            1. SDSWanderer
              15.10.2016 00:37

              Промисы даже без async\await сильно лучше предложенного в первом примере, так как они позволяют думать о данных, а не о контроле выполнения. Второй пример не вымышленный, именно так оно и будет выглядеть с async\await, только вместо придуманного AsyncHttp.request будет вполне реальный fetch. А пример из "реальной жизни" будет выглядеть гораздо лучше если не запихивать промис в переменную, а навешивать then и catch непосредственно, и применить arrow functions:


              fetch('http://google.com')
              .then(result => console.log(result))
              .catch(error => console.log('error:', error))


              1. SDSWanderer
                15.10.2016 01:00
                +1

                Пример который по середине, только по людски:


                Promise.all(urls.map(url => fetch(url)))
                .then(texts => console.log(texts.join('')))

                Вобщем, не пытайтесь придумать велосипед, учите лучше современный javascript, все уже за вас придумали.


  1. tenbits
    14.10.2016 15:41
    +1

    Жаль, что не продуманы 2 вещи — прогресс и отмена. Особенно отмена, или как минимум прерывание цепочки .then(...).then(...).then(...), предлагают самому в каждом then проверять "флаги". И жаль, что always таки не попал в спецификацию.


    1. mediaton
      14.10.2016 16:05
      +1

      обсуждают сейчас


    1. mediaton
      14.10.2016 16:20

      и finally тоже


    1. Ohar
      14.10.2016 17:54

      А что вам мешает делать catch и возвращать ошибку?


    1. Holix
      14.10.2016 21:13
      +2

      Да, жаль. Хотя бы отмена настолько нужна, что мне пришлось самому написать свою версию Promise с хорошей отменой (nano-promise). Суть в том, что стандартный промис лишь наблюдатель за асинхронной операцией. Промис не имеет "рычагов" управления для остановки операции. В nano-promise при создании "Обещания" можно вернуть конструктору объект с методом cancel:


      function get(url) {
        return new Promise(function (resolve, reject) {
          var req = new XMLHttpRequest();
          req.open('GET', url);
      
          req.onload = function() {
            if (req.status == 200)
                resolve(req.response); /* ПРОМИС ВЫПОЛНЕН */
            else
                reject(Error(req.statusText)); /* ПРОМИС ОТКЛОНЁН */
          };
      
          req.onerror = function() { reject(Error("Network Error")); };
          req.send();
      
          // Самое главное
          return { cancel: function () {
             req.abort();
          }};
        });
      
      var ps = get('/index.html');
      ps.cancel(); // отменяем

      Сама отмена не что иное, как вызов reject(Promise.CANCEL_REASON). Ко всему прочему, если есть цепочка из промисов важно корректно отменить всю цепочку с возможными ответвлениями. Т.е. отмена последнего промиса в цепочке порождает отмену до самого начала(при необходимости), а отмена первого спускает отмену (как reject) до самого конца.


  1. Deirel
    14.10.2016 16:57
    -2

    Я извиняюсь, если не в тему — но разве не промайсы?


    1. aeiuo
      14.10.2016 17:17
      +1

      Читается как промис, пишется как промис, зачем промайсы?


    1. ingumsky
      14.10.2016 23:42
      +1

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


      1. Deirel
        20.10.2016 12:40

        Верно, был не прав. Благодарю.


  1. rinatr
    14.10.2016 17:00
    -2

    Блин, как раз имено в сию минуту именно с этими promise вожусь. Правда они реализованы не как-то по своему по ходу.
    Тот же define из require.js + стандартный promise из библиотеки — и приложение ломается.
    Почему так, еще предстоит выяснить, но чёто чувствую, что сделаю как всегда: аля setInterval с проверкой состояния.
    Но может быть сейчас прочитаю эту умную статью и попробую сделать как по уму


  1. darkdaskin
    14.10.2016 18:00

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


    Promise.resolve(1).then(v => q.resolve(v + 1)).then(v => jQuery.Deferred().resolve(v + 1)).then(v => console.log('done', v))

    выведет done 3, как и ожидалось.


  1. adasoft
    15.10.2016 01:48

    Столкнувшись с Promise так и не понял, почему не реализован метод .any() (условно назовём его так), который был по аналогии с .all() выполнил все переданные промисы, но не останавливался при первом reject, а просто сохранял его state.

    Подобный метод может использовать например при загрузке информации с нескольких источников, один или несколько из которых могут быть недоступны по каким либо причинам. Но логика работы при этом не должна нарушаться, просто не добавляются все элементы. Сейчас я просто вызываю последовательно методы загрузки и обрабатываю полученные данные в каждом resolve вместо одного общего.

    Связанный с вышеуказанным пунктом. Возможно организовать динамическую цепочку промисов. Т.е не явно указанную как в примерах Promise.then().then().then(), но итерацию по списку заданных промисов с последовательным выполнение в указанном порядке, с учётом того что результат resolve (а может и reject) передаётся по цепочке от одного промиса к другому. Можно назвать этот метод .each()

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


    1. ArtemNIkolaev
      15.10.2016 01:57
      +1

      Ответ на ваш вопрос можно найти в статье У нас проблемы с промисами или вот под спойлером:


      Продвинутая ошибка №3 — промисы против фабрик промисов

      Допустим, вы хотите выполнить серию промисов один за другим, последовательно. Вы хотите что-то вроде Promise.all(), но такой, чтобы не выполнял промисы параллельно.


      Сгоряча вы можете написать что-то подобное:


      function executeSequentially(promises) {
        var result = Promise.resolve();
        promises.forEach(function (promise) {
          result = result.then(promise);
        });
        return result;
      }

      К сожалению, пример выше не будет работать так, как задумывалось. Промисы из списка, переданного в executeSequentially(), все равно начнут выполняться параллельно.


      Причина в том, что по спецификации промис начинает выполнять заложенную в него логику сразу после создания. Он не будет ждать. Таким образом, не сами промисы, а массив фабрик промисов — это то, что действительно нужно передать в executeSequentially:


      function executeSequentially(promiseFactories) {
        var result = Promise.resolve();
        promiseFactories.forEach(function (promiseFactory) {
          result = result.then(promiseFactory);
        });
        return result;
      }

      Я знаю, вы сейчас думаете: «Кто, черт возьми, этот Java программист, и почему он рассказывает нам о фабриках?». На самом деле фабрика — это простая функция, возвращающая промис:


      function myPromiseFactory() {
        return somethingThatCreatesAPromise();
      }

      Почему этот пример будет работать? А потому, что наша фабрика не создаст промис до тех пор, пока до него не дойдет очередь. Она работает именно как resolveHandler для then().


      Посмотрите внимательно на функцию executeSequentially() и мысленно замените ссылку на promiseFactory ее содержимым — сейчас над вашей головой должна радостно вспыхнуть лампочка :)


      1. adasoft
        15.10.2016 07:35

        «фабрика» это отличный термин. Но «лампочка» не загорается, наверное из-за «магии»


      1. kurtov
        17.10.2016 06:57

        Т.к. я не Java программист, то делаю таким образом:

        function executeSequentially(promises) {
          var result = Promise.resolve();
          promises.forEach(function (promise) {
            // result = result.then(promise);
            result = result.then(() => promise);
          });
          return result;
        }
        


        Через reduce еще изящней
        function executeSequentially(promises) {
          return promises.reduce(function(first, next) {
            return first.then(() => next);
          }, Promise.resolve());
        }
        


        1. vintage
          17.10.2016 08:19

          И оба варианта работают не так, как нужно, прочитайте внимательнее.


          1. kurtov
            17.10.2016 08:54

            Да, вы правы. На самом деле я не использую next как промис. Вместо того чтобы формировать массив промисов, я итерирую массив объектов, по которым нужно сделать последовательные ассинхронные вызовы и в then() создаю анонимную функцию возвращающую промис. Т.е. это как раз фабрика о которой шла речь.

            Виноват, что бездумно упростил свой вариант без проверки и выдал как верный.


      1. mediaton
        20.10.2016 16:46
        +1

        Спасибо большое, как раз пригодилось


  1. vintage
    15.10.2016 19:49

    Небольшая головоломка: синхронный код, но параллельные запросы. Как такое может быть? :-)


    image


    Подсказка: в IE10- запросы идут последовательно.


    1. vintage
      15.10.2016 20:11

      В IE11- точнее. В IE12+ уже параллельно.


  1. netch80
    17.10.2016 12:02

    Почему promise API несимметричен относительно деления на первичные функции (в new) и вторичные (во then/catch)?

    В первичных надо получать аргументами resolve и reject, и вызывать их явно (проверил на promise 7.1.1 для nodejs — простой return со значением игнорируется, как будто промис незавершён).
    Во вторичных надо возвращать значение или генерировать исключение, параметров resolve, reject нет.

    Я бы понял логику, или что
    1) у всех функций-коллбэков возврат значения работает как resolve с этим значением, а генерация исключения — как reject,
    или что
    2) все функции-коллбэки имеют три входных параметра — input, resolve, reject,

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

    Также нет возможности написать .resolve(значение), и аналогично для reject — тоже было бы значительно удобнее (и извне, как уже обсуждают, и изнутри). (Тогда можно было бы вторым параметром передавать сам объект промиса, для вызовов его методов.)

    Или я не вижу каких-то хитростей, которые решают это, и которые можно найти только с заметным опытом их использования?