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

https://hackernoon.com/understanding-promises-in-javascript-13d99df067c1
  • Перевод
Периодически мы публикуем материалы, которые так или иначе касаются использования промисов в JavaScript.


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

Поэтому, если вы хотите лучше понять промисы, мы предлагаем вашему вниманию перевод очередной статьи, посвящённой этой теме. Её автор говорит, что он последние 10 лет занимался разработкой на Java и PHP, но всё это время с интересом поглядывал на JavaScript. Недавно он решил всерьёз заняться JS и первой заинтересовавшей его темой стали промисы.



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

JavaScript глазами новичка


Тот, кто начинает писать на JavaScript, может почувствовать себя, что называется, «не в своей тарелке». Одни говорят, что JS — это синхронный язык программирования, другие утверждают, что он — асинхронный. Новичок слышит о коде, который блокирует главный поток, и о коде, который его не блокирует, о паттернах проектирования, основанных на событиях, о жизненном цикле событий, о стеке вызовов функций, об очереди событий и об их всплытии, о полифиллах. Он узнаёт, что существуют такие штуки, как Babel, Angular, React, Vue и мириады других библиотек. Если вы только что узнали в таком вот «новичке» себя — не стоит из-за этого волноваться. Вы — не первый и не последний. Есть даже термин для этого — так называемая «JavaScript-усталость» (JavaScript fatigue). По этому поводу метко высказался Лукас Ф Коста: «JavaScript-усталость — это то, что можно наблюдать тогда, когда люди используют инструменты, которые им не нужны, для решения проблем, которых у них нет».

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

Простой рассказ о промисах


Слово «promise» переводится как «обещание». Promise-объекты в программировании, которые мы называем «промисами», очень похожи на обычные обещания, которые люди дают друг другу в реальной жизни. Поэтому давайте сначала поговорим о таких вот обещаниях.

В Википедии можно найти следующее определение слова «обещание»: «обязательство, согласие кого-либо что-либо выполнить или, напротив, не делать». В словаре Ожегова «обещание» — это «добровольное обязательство сделать что-нибудь».

Итак, что мы знаем об обещаниях?

  1. Обещание даёт вам гарантию того, что что-либо будет сделано. При этом не имеет значения, кто именно это сделает: тот, кто дал обещание, или кто-то другой, по просьбе того, кто дал обещание. Обещание даёт уверенность в чём-то, основываясь на этой уверенности тот, кто получил обещание, может, например, строить какие-то планы.
  2. Обещание может быть либо выполнено, либо нет.
  3. Если обещание будет выполнено, то вы, в результате, ожидаете чего-либо, что вы сможете использовать в дальнейшем для выполнения каких-либо действий или реализации каких-то планов.
  4. Если обещание оказывается невыполненным, то вам захочется узнать, почему тот, кто его дал, не смог его выполнить. После того, как вы узнаете причину произошедшего и у вас будет уверенность в том, что обещание не выполнено, вы можете размышлять о том, что делать дальше, или о том, как справиться с возникшей ситуацией.
  5. После того, как вам что-то пообещали, всё, что у вас есть — это некая гарантия. Вы не можете воспользоваться тем, что вам обещано, немедленно. Вы можете определить для себя — что понадобится сделать в том случае, если обещание будет выполнено (следовательно, вы получите обещанное), и что понадобится сделать, если оно окажется нарушенным (в таком случае вы знаете причину произошедшего, а следовательно можете продумать запасной план действий).
  6. Есть вероятность, что человек, давший обещание, попросту исчезнет. В подобных случаях полезно, чтобы обещание было бы привязано к каким-то временным рамкам. Например, если тот, кто дал вам обещание, не объявится через 10 дней, можно считать, что у него возникли какие-то проблемы и обещание он нарушил. В результате, даже если тот, кто дал обещание, выполнит его через 15 дней, это не будет иметь значения, так вы уже действуете по альтернативному плану, не полагаясь на обещание.

Теперь переходим к JavaScript.

Промисы в JavaScript


У меня есть одно правило: занимаясь JavaScript, я всегда читаю документацию, которую можно найти на MDN. Мне кажется, что этот ресурс выгодно отличается от остальных конкретностью и чёткостью изложения. Поэтому, изучая промисы, я ознакомился с соответствующим материалом и поэкспериментировал с кодом для того, чтобы привыкнуть к новым для себя синтаксическим конструкциям.

Для того чтобы понять промисы, надо разобраться с двумя основными вещами. Первая — это создание промисов. Вторая — обработка результатов, возвращаемых промисами. Хотя большая часть кода, который мы пишем, направлена на работу с промисами, создаваемыми, например, некими библиотеками, полное понимание механизмов работы промисов, без сомнения, окажется полезным. Кроме того, программисту, который уже имеет некоторый опыт, знать о том, как создавать промисы, так же важно, как знать о том, как с ними работать.

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


Вот как создают промисы:

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

Конструктор принимает функцию, выполняющую некие действия, мы назвали её здесь executor. Эта функция принимает два параметра — resolve и reject, которые, в свою очередь, также являются функциями. Промисы обычно используются для выполнения асинхронных операций или кода, который может заблокировать главный поток, например — такого, который работает с файлами, выполняет вызовы неких API, делает запросы к базам данных, занимается операциями ввода-вывода, и так далее. Запуск подобных асинхронных операций выполняется в функции executor. Если асинхронная операция будет завершена успешно, тогда результат, ожидаемый от промиса, будет возвращён путём вызова функции resolve. Ситуация, в которой вызывается эта функция, определяется создателем промиса. Аналогично, при возникновении ошибки, сведения о том, что случилось, возвращают, вызывая функцию reject.

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

var keepsHisWord;
keepsHisWord = true;
promise1 = new Promise(function(resolve, reject) {
  if (keepsHisWord) {
    resolve("The man likes to keep his word");
  } else {
    reject("The man doesnt want to keep his word");
  }
});
console.log(promise1);

Вот что выведет этот код:


У промиса есть состояние (PromiseStatus) и значение (PromiseValue)

Так как наш промис мгновенно разрешается, исследовать его начальное состояние мы не можем. Поэтому давайте создадим новый промис, которому, для разрешения, нужно некоторое время. Легче всего это сделать, прибегнув к функции setTimeout.

promise2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve({
      message: "The man likes to keep his word",
      code: "aManKeepsHisWord"
    });
  }, 10 * 1000);
});
console.log(promise2);

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


Неразрешённый промис

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


Промис, разрешённый через 10 секунд и возвращающий объект

Взглянем теперь на промис, который мы решили не разрешить, а отклонить. Для этого модифицируем тот код, который уже использовался в первом примере.

keepsHisWord = false;
promise3 = new Promise(function(resolve, reject) {
  if (keepsHisWord) {
    resolve("The man likes to keep his word");
  } else {
    reject("The man doesn't want to keep his word");
  }
});
console.log(promise3);

Так как мы не обрабатываем ситуацию отклонения промиса, в консоли браузера (тут используется Google Chrome), будет выведено сообщение об ошибке. Подробнее мы поговорим об этом ниже.


Отклонённый промис

Теперь, проанализировав все три примера, мы можем видеть, что в PromiseStatus могут появляться три разных значения: pending (ожидание), resolved (успешное разрешение) и rejected (отклонение). Когда промис создаётся, в PromiseStatus будет значение pending, а в PromiseValue будет undefined. Эти значения будут сохраняться до разрешения или отклонения промиса. Когда промис находится в состоянии resolved или rejected, его называют заданным (settled) промисом. Такой промис перешёл из состояния ожидания в состояние, в котором он имеет либо состояние resolved, либо состояние rejected.

Теперь, после того, как мы узнали о том, как создаются промисы, мы можем поговорить о том, как обрабатывать то, что они возвращают. Для того чтобы в этом разобраться, нам понадобится понять устройство объекта Promise.

Объект Promise


В соответствии с документацией MDN, объект Promise представляет собой результат успешного или неудачного завершения асинхронной операции.

У объекта Promise есть статические методы и методы прототипа объекта. Статические методы можно вызывать, не создавая экземпляр объекта, а для вызова методов прототипа нужен экземпляр объекта Promise. Учитывайте, что и статические и обычные методы возвращают объекты Promise. Это упрощает работу.

▍Методы прототипа объекта Promise


Поговорим сначала о методах прототипа объекта Promise. Существует три таких метода. Не забывайте о том, что эти методы можно вызывать у экземпляра объекта Promise, и то, что они сами возвращают промисы. Благодаря всем этим методам можно назначать обработчики, реагирующие на изменение состояния промисов. Как мы уже видели, когда промис создаётся, он находится в состоянии pending. При переходе промиса в состояние resolved или rejected будет вызван, как минимум, один из следующих методов:

Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)

Ниже показана схема работы промиса и события, приводящие к вызову методов .then и .catch. Так как эти методы возвращают объекты Promise, их вызовы можно объединять в цепочки, это также отражено на схеме. Если в промисе предусмотрено использование метода .finally, он будет вызван тогда, когда промис перейдёт в состояние settled, независимо от того, был ли этот промис успешно разрешён или отклонён.


Схема работы промиса (изображение взято отсюда)

Вот небольшая история. Вы — школьник и просите, чтобы ваша мама купила вам мобильник. Она говорит: «Если наши сбережения будут больше, чем стоит телефон, я его тебе куплю». Теперь перескажем эту историю языком JavaScript.

var momsPromise = new Promise(function(resolve, reject) {
  momsSavings = 20000;
  priceOfPhone = 60000;
  if (momsSavings > priceOfPhone) {
    resolve({
      brand: "iphone",
      model: "6s"
    });
  } else {
    reject("We donot have enough savings. Let us save some more money.");
  }
});
momsPromise.then(function(value) {
  console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
});
momsPromise.catch(function(reason) {
  console.log("Mom coudn't buy me the phone because ", reason);
});
momsPromise.finally(function() {
  console.log(
    "Irrespecitve of whether my mom can buy me a phone or not, I still love her"
  );
});

Вот что выведет этот код:


Мама не сдержала обещание

Если же мы изменим значение переменной momsSavings на 200000, тогда мама сможет купить сыну подарок. В таком случае вышеприведённый код выведет следующее.


Мама выполнила обещание

Теперь давайте представим, что рассматриваемый код оформлен в виде библиотеки, а мы этой библиотекой пользуемся. Поговорим об эффективном использовании методов .then и .catch.

Так как методу .then можно назначать и обработчик onFulfilled, вызываемый при успешном разрешении промиса, и обработчик onRejected, вызываемый при отклонении промиса, вместо того, чтобы использовать и метод .then и метод .catch, мы можем добиться того же эффекта с помощью одного лишь метода .then. Вот как это может выглядеть:

momsPromise.then(
  function(value) {
    console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
  },
  function(reason) {
    console.log("Mom coudn't buy me the phone because ", reason);
  }
);

Это — рабочий пример, но для того, чтобы не страдала читабельность кода, лучше, вместо одного универсального .then, использовать методы .then и .catch.

Для того чтобы эти примеры можно было бы запускать в браузере, а конкретно, в Google Chrome, я постарался, чтобы тут не было бы внешних зависимостей. Для того чтобы лучше понять то, что мы рассмотрим далее, давайте создадим функцию, которая возвращает промис, разрешение или отклонение которого происходит случайным образом. Это позволит нам испытать различные сценарии работы с промисами. Для того чтобы разобраться в особенностях работы асинхронных функций, будем задавать в наших промисах случайные задержки. Так как нам нужны случайные числа, создадим функцию, которая возвращает случайное число между x и y. Вот эта функция.

function getRandomNumber(start = 1, end = 10) {
  //предполагается, что при использовании этой функции start и end >=1 и end > start
  return parseInt(Math.random() * end) % (end-start+1) + start;
}

Теперь создадим функцию, которая возвращает промисы. Назовём её promiseTRRARNOSG. Название этой функции расшифровывается как promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator, то есть это — генератор промисов, которые случайным образом разрешаются или отклоняются через случайное число секунд. Эта функция будет создавать промис, который будет разрешён или отклонён через случайный промежуток времени между 2 и 10 секундами. Для того чтобы случайным образом разрешать или отклонять промис, мы получаем случайное число между 1 и 10. Если это число больше 5 — промис будет разрешён, если нет — отклонён.

function getRandomNumber(start = 1, end = 10) {
  //предполагается, что при использовании этой функции start и end >=1 и end > start
  return (parseInt(Math.random() * end) % (end - start + 1)) + start;
}
var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator = function() {
  return new Promise(function(resolve, reject) {
    let randomNumberOfSeconds = getRandomNumber(2, 10);
    setTimeout(function() {
      let randomiseResolving = getRandomNumber(1, 10);
      if (randomiseResolving > 5) {
        resolve({
          randomNumberOfSeconds: randomNumberOfSeconds,
          randomiseResolving: randomiseResolving
        });
      } else {
        reject({
          randomNumberOfSeconds: randomNumberOfSeconds,
          randomiseResolving: randomiseResolving
        });
      }
    }, randomNumberOfSeconds * 1000);
  });
});
var testProimse = promiseTRRARNOSG();
testProimse.then(function(value) {
  console.log("Value when promise is resolved : ", value);
});
testProimse.catch(function(reason) {
  console.log("Reason when promise is rejected : ", reason);
});
// С помощью цикла создадим десять разных промисов с использованием нашей функции для того, чтобы увидеть разные промисы. Некоторые из них будут разрешены, некоторые - отклонены. 
for (i=1; i<=10; i++) {
  let promise = promiseTRRARNOSG();
  promise.then(function(value) {
    console.log("Value when promise is resolved : ", value);
  });
  promise.catch(function(reason) {
    console.log("Reason when promise is rejected : ", reason);
  });
}

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

▍Статические методы объекта Promise


Существует четыре статических методы объекта Promise.

Вот два метода — Promise.reject(reason)и Promise.resolve(value), которые позволяют создавать, соответственно, отклонённые и разрешённые промисы.

Вот как работать с методом Promise.reject, создающим отклонённые промисы.

var promise3 = Promise.reject("Not interested");
promise3.then(function(value){
  console.log("This will not run as it is a rejected promise. The resolved value is ", value);
});
promise3.catch(function(reason){
  console.log("This run as it is a rejected promise. The reason is ", reason);
});

Вот пример использования метода Promise.resolve, создающего успешно разрешённые промисы.

var promise4 = Promise.resolve(1);
promise4.then(function(value){
  console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.catch(function(reason){
  console.log("This will not run as it is a resolved promise", reason);
});

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

var promise4 = Promise.resolve(1);
promise4.then(function(value){
  console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.then(function(value){
  console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
});
promise4.catch(function(reason){
  console.log("This will not run as it is a resolved promise", reason);
});

Вот что он выведет в консоль браузера:


Использование нескольких .then при работе с промисом

Следующие два метода, Promise.all и Promise.race, предназначены для работы с наборами промисов. Если, для решения некоей задачи, надо обрабатывать несколько промисов, удобнее всего поместить эти промисы в массив, а затем выполнять с ними необходимые действия. Для того чтобы понять сущность рассматриваемых здесь методов, мы не сможем использовать нашу удобную функцию promiseTRRARNOSG, так как результат её работы слишком сильно зависит от воли случая. Нам удобнее будет воспользоваться чем-то таким, что выдаёт более предсказуемые промисы, что позволит нам понять их поведение. Поэтому создадим две новых функции. Одна из них (promiseTRSANSG) будет создавать промисы, которые разрешаются через n секунд, вторая (promiseTRJANSG ) — промисы, которые через n секунд отклоняются.

var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
  n = 0
) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve({
        resolvedAfterNSeconds: n
      });
    }, n * 1000);
  });
});
var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
  n = 0
) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject({
        rejectedAfterNSeconds: n
      });
    }, n * 1000);
  });
});

Теперь воспользуемся этими функциями для того, чтобы понять особенности работы метода Promise.all.

▍Метод Promise.all


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

Пример №1


Здесь будут разрешены все промисы. Такой сценарий встречается чаще всего.

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(2));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.log("One of the promises failed with the following reason", reason);
});

Вот что этот код выведет в консоль:


Все промисы разрешены

Проанализировав результаты выполнения этого примера, можно сделать два важных наблюдения.

Во-первых, третий промис, на разрешение которого нужно 2 секунды, завершается до завершения второго, но, как можно видеть из вывода, формируемого кодом, порядок промисов в массиве сохраняется.

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

Если бы промисы выполнялись последовательно, то время выполнения этой инструкции составило бы 7 секунд (1+4+2). Однако таймер сообщает нам о том, что вся эта операция заняла, если округлить результат, 4 секунды. Это является доказательством того, что все промисы выполняются параллельно.

Пример№ 2


Теперь рассмотрим ситуацию, когда в массиве, переданном Promise.all, нет промисов. Полагаю, такой вариант применения этой функции встречается реже всего.

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(1);
promisesArray.push(4);
promisesArray.push(2);
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.log("One of the promises failed with the following reason", reason);
});

Вот какой вывод сформирует этот код:


Вызов Promise.all с передачей этому методу массива, не содержащего промисов

Так как в массиве нет промисов, Promise.all почти мгновенно разрешается.

Пример №3


Теперь посмотрим на то, что происходит в том случае, когда один из промисов, переданных Promise.all, оказывается отклонённым.

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRJANSG(2));
promisesArray.push(promiseTRSANSG(4));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.timeEnd("Promise.All");
  console.log("One of the promises failed with the following reason ", reason);
});

Как видно из результатов выполнения кода, показанных ниже, выполнение Promise.all останавливается после первого отклонённого промиса с выводом сообщения, которое выдаёт этот промис.


Выполнение останавливается после первого отклонённого промиса

▍Метод Promise.race


MDN сообщает, что метод Promise.race(iterable)возвращает разрешённый или отклонённый промис со значением или причиной отклонения, после того как один из переданных промисов будет, соответственно, разрешён или отклонён.

Рассмотрим примеры работы с Promise.race.

Пример №1


Здесь показано, что происходит в случае, когда один из промисов, переданных Promise.race, разрешается раньше всех.

console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRSANSG(2));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
  console.timeEnd("Promise.race");
  console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
  console.timeEnd("Promise.race");
  console.log("The fastest promise rejected with the following reason ", reason);
});

Вот что попадает в консоль после выполнения этого примера.


Промис, который разрешился быстрее всех остальных

Все промисы здесь выполняются параллельно. Третий промис разрешается через 2 секунды. Как только это произойдёт, промис, возвращаемый Promise.race, оказывается разрешённым.

Пример №2


Теперь рассмотрим ситуацию, когда один из промисов, переданных Promise.race, оказывается отклонённым.

console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(6));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
  console.timeEnd("Promise.race");
  console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
  console.timeEnd("Promise.race");
  console.log("The fastest promise rejected with the following reason ", reason);
});

В консоль, после выполнения этого примера, попадёт следующее:


Промис, отклонённый раньше всех

Промисы здесь, как и в предыдущих примерах, выполняются параллельно. Четвёртый промис отклоняется через 3 секунды. Как только это происходит, промис, возвращаемый Promise.race, оказывается отклонённым.

Общий пример и эксперименты


Я собрал все примеры, которые мы рассматривали в этом материале, в одном месте, что позволит удобнее с ними экспериментировать, исследовать различные сценарии работы с промисами. Этот код рассчитан на выполнение в браузере, поэтому тут мы не используем вызовов каких-либо API, не обращаемся к файловым операциям, не работаем с базами данных. Хотя всё это находит применение при разработке реальных проектов, полагаю, работа с этими механизмами может отвлечь нас от нашей главной цели — промисов. А использование простых функций, имитирующих, временные задержки, даёт похожие результаты и не обременяет нас дополнительными деталями.

Самостоятельно исследуя эти примеры, вы можете поэкспериментировать с кодом, со значениями переменных, изучить разные сценарии использования промисов. В частности, вы можете воспользоваться комбинацией методов promiseTRJANSG, promiseTRSANSG и promiseTRRARNOSG для того, чтобы сымитировать множество сценариев использования промисов, что позволит вам лучше их понять. Кроме того, обратите внимание на то, что использование команды console.time позволяет выяснить время, необходимое на выполнение некоего фрагмента кода, и, например, узнать, параллельно или последовательно выполняются промисы. Вот ссылка на gist-страницу с кодом. И, кстати, если хотите — взгляните на библиотеку Bluebird, содержащую некоторые интересные методы для работы с промисами.

Итоги


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

  1. Используйте промисы в ситуациях, когда вы работаете с асинхронным или блокирующим кодом.
  2. Для обработки ситуации успешного разрешения промиса используйте метод .then, для тех случаев, когда промис отклонён, применяйте .catch.
  3. Используйте методы .then и .catch во всех промисах.
  4. Если что-то нужно сделать и при разрешении, и при отклонении промиса, воспользуйтесь методом .finally.
  5. Состояние промиса, после того, как он оказывается разрешённым или отклонённым, уже не меняется.
  6. К одному промису можно добавлять несколько обработчиков, объединяя их вызовы в цепочки.
  7. Все методы объекта Promise, являются ли они статическими методами, или методами прототипа объекта, возвращают промисы.
  8. Метод Promise.all не меняет порядок промисов в переданной ему структуре данных, он не привязывает его к очерёдности разрешения промисов.

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

Уважаемые читатели! Скажите, испытывали ли вы сложности с освоением промисов, когда впервые с ними столкнулись?

КОЕ-ЧТО ВАЖНОЕ И ЦЕННОЕ, ДЛЯ САМЫХ ДОРОГИХ И БЛИЗКИХ
Промо-код на скидку в 10% на наши виртуальные сервера:



Пользуйтесь на здоровье :)
RUVDS.com 778,04
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией
Комментарии 12
    –2
    Пс, там уже async/await в Javascript завезли.
    Рассказывать про промисы лучше начинать с них, тогда всё понятнее будет.
      +8
      Имхо ровно наоборот, ибо под async/await лежат как раз промисы
        0
        Да. но ты сначала показываешь человеку

        try{ 
          let users = apiClient.getUsers();
        } 
        catch (e) {
         ...
        }


        А потом уже можно рассказывать про то, что там под капотом промисы на самом деле.
        Если вы действительно хотите человека чему-то научить, но надо действовать от простого к сложному, итеративно.
        Я, кстати, не думаю, что с появлением async/await люди часто продолжают писать .then

          +1
          Я, кстати, не думаю, что с появлением async/await люди часто продолжают писать .then

          Ради интереса посмотрел в своих текущих проектах: 39 .then(, 403 await. Правда слоновья доля await в тестах. В самих проектах в основном всё синхронно и почти вся асинхронщина на nodejs-сервере.

      +5

      Казалось бы, 2018.

        +2
        и в том, что в ней достаточно сложно разобраться.

        — в чем именно в промисах сложно разобраться?
        — в промисах.
          0
          А ничего, что finally пока что в Stage 4? Хоть бы упомянули об этом.
            0
            Дак вроде как это вошло в уже в ES2018
              –1
              Так он тоже отнюдь не везде поддерживается.
                0
                Во первых, статья про язык, а не про то где какие его фичи поддерживаются.

                Во вторых, это не проблема, берете babel и вперед.
            +1
            Для обработки ситуации успешного разрешения промиса используйте метод .then, для тех случаев, когда промис отклонён, применяйте .catch.


            Это немножко неосторожный совет.
            Например, пишем:
            промис.then(обработчик1).catch(обработчик2)

            Верно ли, что в обработчик2 мы попадаем тогда и только тогда, когда исходный промис отклонён?
            Нет, мы попадём туда, когда отклонён промис промис.then(обработчик1), то есть и в том случае, когда исходный промис выполнен, а в обработчик1 брошена ошибка.
            То есть это не эквивалентно
            промис.then(обработчик1, обработчик2)

            Использовать catch следует с осторожностью.
              0
              Да, эти конструкции не эквивалентны. Тем не менее, обычно именно это и нужно: отловить любую ошибку, в том числе возникшую уже в первом обработчике. Потому-то и советуют заменять двойной then на catch.

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

            Самое читаемое