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

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

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

  • « Предыдущая статья
  • Следующая статья »

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

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

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

function doSomethingOldStyle(successCallback, failureCallback) {
  console.log("Готово.");
  // Успех в половине случаев.
  if (Math.random() > .5) {
    successCallback("Успех")
  } else {
    failureCallback("Ошибка")
  }
}

function successCallback(result) {
  console.log("Успешно завершено с результатом " + result);
}

function failureCallback(error) {
  console.log("Завершено с ошибкой " + error);
}

doSomethingOldStyle(successCallback, failureCallback);

…современные функции возвращают промис, в который вы записываете ваши колбэки:

function doSomething() {
  return new Promise((resolve, reject) => {
    console.log("Готово.");
    // Успех в половине случаев.
    if (Math.random() > .5) {
      resolve("Успех")
    } else {
      reject("Ошибка")
    }
  })
}

const promise = doSomething();
promise.then(successCallback, failureCallback);

…или просто:

doSomething().then(successCallback, failureCallback);

Мы называем это асинхронным вызовом функции. У этого соглашения есть несколько преимуществ. Давайте рассмотрим их.

Гарантии

В отличие от старомодных переданных колбэков промис даёт некоторые гарантии:

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

Но наиболее непосредственная польза от промисов — цепочка вызовов (chaining).

Цепочка вызовов

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

Вот в чём магия: функция then возвращает новый промис, отличающийся от первоначального:

let promise = doSomething();
let promise2 = promise.then(successCallback, failureCallback);

или

let promise2 = doSomething().then(successCallback, failureCallback);

Второй промис представляет завершение не только doSomething(), но и функций successCallback или failureCallback, переданных вами, а они тоже могут быть асинхронными функциями, возвращающими промис. В этом случае все колбэки, добавленные к promise2 будут поставлены в очередь за промисом, возвращаемым successCallback или failureCallback.

По сути, каждый вызванный промис означает успешное завершение предыдущих шагов в цепочке.

Раньше выполнение нескольких асинхронных операций друг за другом приводило к классической «Вавилонской башне» колбэков:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Итоговый результат: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

В современных функциях мы записываем колбэки в возвращаемые промисы — формируем цепочку промисов:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Итоговый результат: ' + finalResult);
})
.catch(failureCallback);

Аргументы then необязательны, а catch(failureCallback) — это сокращение для then(null, failureCallback). Вот как это выражено с помощью стрелочных функций:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Итоговый результат: ${finalResult}`);
})
.catch(failureCallback);

Важно: Всегда возвращайте промисы в return, иначе колбэки не будут сцеплены и ошибки могут быть не пойманы (стрелочные функции неявно возвращают результат, если скобки {} вокруг тела функции опущены).

Цепочка вызовов после catch

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

new Promise((resolve, reject) => {
    console.log('Начало');

    resolve();
})
.then(() => {
    throw new Error('Где-то произошла ошибка');

    console.log('Выведи это');
})
.catch(() => {
    console.log('Выведи то');
})
.then(() => {
    console.log('Выведи это, несмотря ни на что');
});

В результате выведется данный текст:

Начало
Выведи то
Выведи это, несмотря ни на что

Заметьте, что текст «Выведи это» не вывелся, потому что «Где-то произошла ошибка» привела к отказу

Распространение ошибки

Вы могли ранее заметить, что failureCallback повторяется три раза в «pyramid of doom», а в цепочке промисов всего лишь один раз:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Итоговый результат: ${finalResult}`))
.catch(failureCallback);

В основном, цепочка промисов останавливает выполнение кода, если где-либо произошла ошибка, и вместо этого ищет далее по цепочке обработчики ошибок. Это очень похоже на то, как работает синхронный код:

try {
  let result = syncDoSomething();
  let newResult = syncDoSomethingElse(result);
  let finalResult = syncDoThirdThing(newResult);
  console.log(`Итоговый результат: ${finalResult}`);
} catch(error) {
  failureCallback(error);
}

Эта симметрия с синхронным кодом лучше всего показывает себя в синтаксическом сахаре async/await в ECMAScript 2017:

async function foo() {
  try {
    let result = await doSomething();
    let newResult = await doSomethingElse(result);
    let finalResult = await doThirdThing(newResult);
    console.log(`Итоговый результат: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}

Работа данного кода основана на промисах. Для примера здесь используется функция doSomething(), которая встречалась ранее. Вы можете прочитать больше о синтаксисе здесь

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

Создание промиса вокруг старого колбэка

Promise может быть создан с помощью конструктора. Это может понадобится только для старых API.

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

setTimeout(() => saySomething("10 seconds passed"), 10000);

Смешивание старого колбэк-стиля и промисов проблематично. В случае неудачного завершения saySomething или программной ошибки, нельзя обработать ошибку.

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

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);

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

Композиция

Promise.resolve() и Promise.reject() короткий способ создать уже успешные или отклонённые промисы соответственно. Это иногда бывает полезно.

Promise.all() и Promise.race() — два метода запустить асинхронные операции параллельно.

Последовательное выполнение композиции возможно при помощи хитрости JavaScript:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

Фактически, мы превращаем массив асинхронных функций в цепочку промисов равносильно: Promise.resolve().then(func1).then(func2);

Это также можно сделать, объединив композицию в функцию, в функциональном стиле программирования:

const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

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

const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
transformData(data);

В ECMAScript 2017, последовательные композиции могут быть выполнены более простым способом с помощью async/await:

for (const f of [func1, func2]) {
  await f();
}

Порядок выполнения

Чтобы избежать сюрпризов, функции, переданные в then никогда не будут вызваны синхронно, даже с уже разрешённым промисом:

Promise.resolve().then(() => console.log(2));
console.log(1); // 1, 2

Вместо немедленного выполнения, переданная функция встанет в очередь микрозадач, а значит выполнится, когда очередь будет пустой в конце текущего вызова JavaScript цикла событий (event loop), т.е. очень скоро:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

Вложенность

Простые цепочки promise лучше оставлять без вложений, так как вложенность может быть результатом небрежной структуры. Смотрите распространённые ошибки.

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

doSomethingCritical()
.then(result => doSomethingOptional()
  .then(optionalResult => doSomethingExtraNice(optionalResult))
  .catch(e => {})) // Игнорируется если необязательные параметр не выкинул исключение
.then(() => moreCriticalStuff())
.catch(e => console.log("Критическая ошибка: " + e.message));

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

Внутренний оператор catch нейтрализует и перехватывает ошибки только от doSomethingOptional() и doSomethingExtraNice(), после чего код возобновляется с помощью moreCriticalStuff(). Важно, что в случае сбоя doSomethingCritical() его ошибка перехватывается только последним (внешним) catch.

Частые ошибки

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

// Плохой пример! Три ошибки!

doSomething().then(function(result) {
  doSomethingElse(result) // Забыл вернуть промис из внутренней цепочки + неуместное влаживание
  .then(newResult => doThirdThing(newResult));
}).then(() => doFourthThing());
// Забыл закончить цепочку методом catch

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

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

Третья ошибка это забыть закончить цепочку ключевым словом catch. Незаконченные цепочки приводят к необработанным отторжениям промисов в большинстве браузеров.

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

doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(newResult => doThirdThing(newResult))
.then(() => doFourthThing())
.catch(error => console.log(error));

Обратите внимание что () => x это сокращённая форма () => { return x; }.

Теперь у нас имеется единственная определённая цепочка с правильной обработкой ошибок.

Использование async/await предотвращает большинство, если не все вышеуказанные ошибки, но взамен появляется другая частая ошибка — забыть ключевое слово await.

Смотрите также

Последнее обновление: 30.08.2021

Одним из преимуществ промисов является более простая обработка ошибок. Для получения и обработки ошибки мы можем использовать
функцию catch() объекта Promise, которая в качестве параметра принимает функцию обработчика ошибки:

const myPromise = new Promise(function(resolve, reject){
	console.log("Выполнение асинхронной операции");
	reject("Переданы некорректные данные");
});
myPromise.catch( function(error){
	console.log(error);
});

Функция catch() в качестве параметра принимает обработчик ошибки. Параметром этой функции-обработчика является то значение,
которое передается в reject().

Консольный вывод:

Выполнение асинхронной операции
Переданы некорректные данные

Генерация ошибки

Выше для извещения о возникшей ошибке вызывалась функция reject(). Но стоит отметить, что ошибка может возникнуть и без вызова функции
reject(). И если в выполняемой в промисе операции генерируется ошибка в силу тех или иных причин, то вся операция также завершается ошибкой.
Например, в следующем коде вызывается нигде не определенная функция getSomeWork():

const myPromise = new Promise(function(resolve){
	console.log("Выполнение асинхронной операции");
	getSomeWork();		// вызов не существующей функции
	resolve("Hello world!");
});
myPromise.catch( function(error){
	console.log(error);
});

Поскольку функция getSomeWork() нигде не объявлена, то выполнение асинхронной задачи завершится ошибкой и не дойдет до вызова resolve("Hello world!").
Поэтому сработает функция обработки ошибок из catch(), которая через параметр error получит информацию о возникшей ошибке, и
в консоли браузера мы увидим сообщение об ошибке:

Выполнение асинхронной операции
ReferenceError: getSomeWork is not defined
    at index.html:39
    at new Promise (<anonymous>)
    at index.html:37

throw

Также ошибка может быть результатом вызова оператора throw, который генерирует ошибку:

cconst myPromise = new Promise(function(resolve, reject){
	console.log("Выполнение асинхронной операции");
	const parsed = parseInt("Hello");
	if (isNaN(parsed)) { 
		throw "Not a number";			// Генерируем ошибку
	}
	resolve(parsed);
});
myPromise.catch( function(error){
	console.log(error);
});

Здесь парсится в число случайная строка. И если результат парсинга не представляет число, то с помощью оператора throw генерируем ошибку.
Это придет к завершению всей функции с ошибкой. И в итоге результат будет обработан функцией catch:

Выполнение асинхронной операции
Not a number

В этом случае функция обработчика получает сообщение об оошибке, который указывается после оператора throw.

Данная ситуация может показаться искуственной, так как нам нет смысла генерировать в коде выше ошибку с помощью throw, поскольку в этом случае мы также
можем передать сообщение об ошибке в функцию reject:

if (isNaN(parsed)) { 
	reject("Not a number");
}

Однако данный оператор может применяться во внешней функции, которую мы вызываем в коде:

function getNumber(str){
	const parsed = parseInt(str);
	if (isNaN(parsed)) throw "Not a number";			// Генерируем ошибку
	else return parsed;
}
const myPromise = new Promise(function(resolve){
	console.log("Выполнение асинхронной операции");
	const result = getNumber("hello");
	resolve(result);
});
myPromise.catch( function(error){
	console.log(error);
});

Здесь парсинг строки в число вынесен во внешнюю функцию — getNumber, однако при вызове этой функции в промисе, также из оператора throw возникнет ошибка.
И соответственно будет выполняться функция catch(), где роизойдет обработка ошибки.

try..catch

Как и в общем случае, операции, которые могут генерировать ошибку, можно помещать в конструкцию try..catch, а при возникновении исключения в блоке catch вызывать функцию reject():

const myPromise = new Promise(function(resolve, reject){
	try{
		console.log("Выполнение асинхронной операции");
		getSomeWork();		// вызов не существующей функции
		resolve("Hello world!");
	}
	catch(err){
		reject(`Произошла ошибка: ${err.message}`);
	}
});
myPromise.catch( function(error){
	console.log(error);
});

Консольный вывод:

Выполнение асинхронной операции
Произошла ошибка: getSomeWork is not defined

Обработка ошибки с помощью функции then

Кроме функции catch для получения информации об ошибке и ее обработки также можно использовать функцию
then() — ее второй параметр представляет обработчик ошибки, который в качестве параметра получает переданное из функции
reject значение:

promise
  .then(function(value){
    // получение значения
  },
  function(error){
    // обработка ошибки
  });
 

Второй параметр функции then() представляет функцию обработчика ошибок. С помощью параметра error в функции-обработчика мы можем получить переданное в reject() значение, либо информацию о возникшей ошибке.

Рассмотрим следуюший пример:

function generateNumber(str){ 
	return new Promise(function(resolve, reject){
		const parsed = parseInt(str);
		if (isNaN(parsed))	reject("значение не является числом")
		else resolve(parsed);
	})
	.then(function(value){ console.log("Результат операции:", value);}, 
		function(error){ console.log("Возникла ошибка:", error);});
}

generateNumber("23");
generateNumber("hello");

В данном случае для того, чтобы в промис можно было передать разные данные, он определен как возващаемый результат функции generateNumber(). То есть в данном случае консольный вывод будет следующим:

Результат операции: 23
Возникла ошибка: значение не является числом

Содержание

  1. Промисы: обработка ошибок
  2. Неявный try…catch
  3. Пробрасывание ошибок
  4. Необработанные ошибки
  5. Promise
  6. Что такое Promise?
  7. Пример с setTimeout
  8. Promise после reject/resolve – неизменны
  9. Промисификация
  10. Цепочки промисов
  11. Перехват ошибок
  12. Промисы в деталях

Промисы: обработка ошибок

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

Например, в представленном ниже примере для fetch указана неправильная ссылка (сайт не существует), и .catch перехватывает ошибку:

Как видно, .catch не обязательно должен быть сразу после ошибки, он может быть далее, после одного или даже нескольких .then

Или, может быть, с сервером всё в порядке, но в ответе мы получим некорректный JSON. Самый лёгкий путь перехватить все ошибки – это добавить .catch в конец цепочки:

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

Неявный try…catch

Вокруг функции промиса и обработчиков находится «невидимый try..catch «. Если происходит исключение, то оно перехватывается, и промис считается отклонённым с этой ошибкой.

Например, этот код:

…Работает так же, как и этот:

«Невидимый try..catch » вокруг промиса автоматически перехватывает ошибку и превращает её в отклонённый промис.

Это работает не только в функции промиса, но и в обработчиках. Если мы бросим ошибку ( throw ) из обработчика ( .then ), то промис будет считаться отклонённым, и управление перейдёт к ближайшему обработчику ошибок.

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

Финальный .catch перехватывает как промисы, в которых вызван reject , так и случайные ошибки в обработчиках.

Пробрасывание ошибок

Как мы уже заметили, .catch ведёт себя как try..catch . Мы можем иметь столько обработчиков .then , сколько мы хотим, и затем использовать один .catch в конце, чтобы перехватить ошибки из всех обработчиков.

В обычном try..catch мы можем проанализировать ошибку и повторно пробросить дальше, если не можем её обработать. То же самое возможно для промисов.

Если мы пробросим ( throw ) ошибку внутри блока .catch , то управление перейдёт к следующему ближайшему обработчику ошибок. А если мы обработаем ошибку и завершим работу обработчика нормально, то продолжит работу ближайший успешный обработчик .then .

В примере ниже .catch успешно обрабатывает ошибку:

Здесь блок .catch завершается нормально. Поэтому вызывается следующий успешный обработчик .then .

В примере ниже мы видим другую ситуацию с блоком .catch . Обработчик (*) перехватывает ошибку и не может обработать её (например, он знает как обработать только URIError ), поэтому ошибка пробрасывается далее:

Управление переходит от первого блока .catch (*) к следующему (**) , вниз по цепочке.

Необработанные ошибки

Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить .catch в конец цепочки, как здесь:

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

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

Что происходит, когда обычная ошибка не перехвачена try..catch ? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса.

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

В браузере мы можем поймать такие ошибки, используя событие unhandledrejection :

Это событие является частью стандарта HTML.

Если происходит ошибка, и отсутствует её обработчик, то генерируется событие unhandledrejection , и соответствующий объект event содержит информацию об ошибке.

Обычно такие ошибки неустранимы, поэтому лучше всего – информировать пользователя о проблеме и, возможно, отправить информацию об ошибке на сервер.

Источник

Promise

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/promise-basics.

Promise (обычно их так и называют «промисы») – предоставляют удобный способ организации асинхронного кода.

В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже.

Что такое Promise?

Promise – это специальный объект, который содержит своё состояние. Вначале pending («ожидание»), затем – одно из: fulfilled («выполнено успешно») или rejected («выполнено с ошибкой»).

На promise можно навешивать колбэки двух типов:

  • onFulfilled – срабатывают, когда promise в состоянии «выполнен успешно».
  • onRejected – срабатывают, когда promise в состоянии «выполнен с ошибкой».

Способ использования, в общих чертах, такой:

  1. Код, которому надо сделать что-то асинхронно, создаёт объект promise и возвращает его.
  2. Внешний код, получив promise , навешивает на него обработчики.
  3. По завершении процесса асинхронный код переводит promise в состояние fulfilled (с результатом) или rejected (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.

Синтаксис создания Promise :

Универсальный метод для навешивания обработчиков:

  • onFulfilled – функция, которая будет вызвана с результатом при resolve .
  • onRejected – функция, которая будет вызвана с ошибкой при reject .

С его помощью можно назначить как оба обработчика сразу, так и только один:

Для того, чтобы поставить обработчик только на ошибку, вместо .then(null, onRejected) можно написать .catch(onRejected) – это то же самое.

Если в функции промиса происходит синхронный throw (или иная ошибка), то вызывается reject :

Посмотрим, как это выглядит вместе, на простом примере.

Пример с setTimeout

Возьмём setTimeout в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом «result»:

В результате запуска кода выше – через 1 секунду выведется «Fulfilled: result».

А если бы вместо resolve(«result») был вызов reject(«error») , то вывелось бы «Rejected: error». Впрочем, как правило, если при выполнении возникла проблема, то reject вызывают не со строкой, а с объектом ошибки типа new Error :

Конечно, вместо setTimeout внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал resolve или reject , которые передадут результат обработчикам.

Функции resolve/reject принимают ровно один аргумент – результат/ошибку.

Именно он передаётся обработчикам в .then , как можно видеть в примерах выше.

Promise после reject/resolve – неизменны

Заметим, что после вызова resolve/reject промис уже не может «передумать».

Когда промис переходит в состояние «выполнен» – с результатом (resolve) или ошибкой (reject) – это навсегда.

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

Последующие вызовы resolve/reject будут просто проигнорированы.

А так – наоборот, ошибка будет раньше:

Промисификация

Промисификация – это когда берут асинхронную функциональность и делают для неё обёртку, возвращающую промис.

После промисификации использование функциональности зачастую становится гораздо удобнее.

В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.

Функция httpGet(url) будет возвращать промис, который при успешной загрузке данных с url будет переходить в fulfilled с этими данными, а при ошибке – в rejected с информацией об ошибке:

Как видно, внутри функции объект XMLHttpRequest создаётся и отсылается как обычно, при onload/onerror вызываются, соответственно, resolve (при статусе 200) или reject .

Заметим, что ряд современных браузеров уже поддерживает fetch – новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем httpGet . И – да, этот метод использует промисы. Полифил для него доступен на https://github.com/github/fetch.

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

«Чейнинг» (chaining), то есть возможность строить асинхронные цепочки из промисов – пожалуй, основная причина, из-за которой существуют и активно используются промисы.

Например, мы хотим по очереди:

  1. Загрузить данные посетителя с сервера (асинхронно).
  2. Затем отправить запрос о нём на github (асинхронно).
  3. Когда это будет готово, вывести его github-аватар на экран (асинхронно).
  4. …И сделать код расширяемым, чтобы цепочку можно было легко продолжить.

Вот код для этого, использующий функцию httpGet , описанную выше:

Самое главное в этом коде – последовательность вызовов:

При чейнинге, то есть последовательных вызовах .then…then…then , в каждый следующий then переходит результат от предыдущего. Вызовы console.log оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.

Если очередной then вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.

  1. Функция в первом then возвращает «обычное» значение user . Это значит, что then возвратит промис в состоянии «выполнен» с user в качестве результата. Он станет аргументом в следующем then .
  2. Функция во втором then возвращает промис (результат нового вызова httpGet ). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий then с его результатом.
  3. Третий then ничего не возвращает.

Схематично его работу можно изобразить так:

Значком «песочные часы» помечены периоды ожидания, которых всего два: в исходном httpGet и в подвызове далее по цепочке.

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

То есть, логика довольно проста:

  • В каждом then мы получаем текущий результат работы.
  • Можно его обработать синхронно и вернуть результат (например, применить JSON.parse ). Или же, если нужна асинхронная обработка – инициировать её и вернуть промис.

Обратим внимание, что последний then в нашем примере ничего не возвращает. Если мы хотим, чтобы после setTimeout (*) асинхронная цепочка могла быть продолжена, то последний then тоже должен вернуть промис. Это общее правило: если внутри then стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.

В данном случае промис должен перейти в состояние «выполнен» после срабатывания setTimeout .

Строку (*) для этого нужно переписать так:

Теперь, если к цепочке добавить ещё then , то он будет вызван после окончания setTimeout .

Перехват ошибок

Выше мы рассмотрели «идеальный случай» выполнения, когда ошибок нет.

А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?

Да мало ли, где ошибка…

Правило здесь очень простое.

При возникновении ошибки – она отправляется в ближайший обработчик onRejected .

Такой обработчик нужно поставить через второй аргумент .then(. onRejected) или, что то же самое, через .catch(onRejected) .

Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим catch в конец нашей цепочки:

В примере выше ошибка возникает в первом же httpGet , но catch с тем же успехом поймал бы ошибку во втором httpGet или в JSON.parse .

Принцип очень похож на обычный try..catch : мы делаем асинхронную цепочку из .then , а затем, в том месте кода, где нужно перехватить ошибки, вызываем .catch(onRejected) .

Обработчик .catch(onRejected) получает ошибку и должен обработать её.

Есть два варианта развития событий:

  1. Если ошибка не критичная, то onRejected возвращает значение через return , и управление переходит в ближайший .then(onFulfilled) .
  2. Если продолжить выполнение с такой ошибкой нельзя, то он делает throw , и тогда ошибка переходит в следующий ближайший .catch(onRejected) .

Это также похоже на обычный try..catch – в блоке catch ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает throw . Существенное отличие – в том, что промисы асинхронные, поэтому при отсутствии внешнего .catch ошибка не «вываливается» в консоль и не «убивает» скрипт.

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

Промисы в деталях

Самым основным источником информации по промисам является, разумеется, стандарт.

Чтобы наше понимание промисов было полным, и мы могли с лёгкостью разрешать сложные ситуации, посмотрим внимательнее, что такое промис и как он работает, но уже не в общих словах, а детально, в соответствии со стандартом ECMAScript.

Согласно стандарту, у объекта new Promise(executor) при создании есть четыре внутренних свойства:

  • PromiseState – состояние, вначале «pending».
  • PromiseResult – результат, при создании значения нет.
  • PromiseFulfillReactions – список функций-обработчиков успешного выполнения.
  • PromiseRejectReactions – список функций-обработчиков ошибки.

Когда функция-executor вызывает reject или resolve , то PromiseState становится «resolved» или «rejected» , а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь «PromiseJobs» .

Эта очередь автоматически выполняется, когда интерпретатору «нечего делать». Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как setTimeout(. 0) .

Исключение из этого правила – если resolve возвращает другой Promise . Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.

Добавляет обработчики в списки один метод: .then(onResolved, onRejected) . Метод .catch(onRejected) – всего лишь сокращённая запись .then(null, onRejected) .

Он делает следующее:

  • Если PromiseState == «pending» , то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.
  • Иначе обработчики сразу помещаются в очередь на выполнение.

Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно – после (выполнятся в ближайшее время, через асинхронную очередь).

Источник

JavaScript Promise Tutorial – How to Resolve or Reject Promises in JS

Promises are important building blocks for asynchronous operations in JavaScript. You may think that promises are not so easy to understand, learn, and work with. And trust me, you are not alone!

Promises are challenging for many web developers, even after spending years working with them.

In this article, I want to try to change that perception while sharing what I’ve learned about JavaScript Promises over the last few years. Hope you find it useful.

What is a Promise in JavaScript?

A Promise is a special JavaScript object. It produces a value after an asynchronous (aka, async) operation completes successfully, or an error if it does not complete successfully due to time out, network error, and so on.

Successful call completions are indicated by the resolve function call, and errors are indicated by the reject function call.

You can create a promise using the promise constructor like this:

let promise = new Promise(function(resolve, reject) {    
    // Make an asynchronous call and either resolve or reject
});

In most cases, a promise may be used for an asynchronous operation. However, technically, you can resolve/reject on both synchronous and asynchronous operations.

Hang on, don’t we have callback functions for async operations?

Oh, yes! That’s right. We have callback functions in JavaScript. But, a callback is not a special thing in JavaScript. It is a regular function that produces results after an asynchronous call completes (with success/error).

The word ‘asynchronous’ means that something happens in the future, not right now. Usually, callbacks are only used when doing things like network calls, or uploading/downloading things, talking to databases, and so on.

While callbacks are helpful, there is a huge downside to them as well. At times, we may have one callback inside another callback that’s in yet another callback and so on. I’m serious! Let’s understand this «callback hell» with an example.

How to Avoid Callback Hell – PizzaHub Example

Let’s order a Veg Margherita pizza 🍕 from the PizzaHub. When we place the order, PizzaHub automatically detects our location, finds a nearby pizza restaurant, and finds if the pizza we are asking for is available.

If it’s available, it detects what kind of beverages we get for free along with the pizza, and finally, it places the order.

If the order is placed successfully, we get a message with a confirmation.

So how do we code this using callback functions? I came up with something like this:

function orderPizza(type, name) {
    
    // Query the pizzahub for a store
    query(`/api/pizzahub/`, function(result, error){
       if (!error) {
           let shopId = result.shopId;
           
           // Get the store and query pizzas
           query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
               if (!error) {
                   let pizzas = result.pizzas;
                   
                   // Find if my pizza is availavle
                   let myPizza = pizzas.find((pizza) => {
                       return (pizza.type===type && pizza.name===name);
                   });
                   
                   // Check for the free beverages
                   query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
                       if (!error) {
                           let beverage = result.id;
                           
                           // Prepare an order
                           query(`/api/order`, {'type': type, 'name': name, 'beverage': beverage}, function(result, error){
                              if (!error) {
                                  console.log(`Your order of ${type} ${name} with ${beverage} has been placed`);
                              } else {
                                  console.log(`Bad luck, No Pizza for you today!`);
                              }
                           });

                       }
                   })
               }
           });
       } 
    });
}

// Call the orderPizza method
orderPizza('veg', 'margherita');

Let’s have a close look at the orderPizza function in the above code.

It calls an API to get your nearby pizza shop’s id. After that, it gets the list of pizzas available in that restaurant. It checks if the pizza we are asking for is found and makes another API call to find the beverages for that pizza. Finally the order API places the order.

Here we use a callback for each of the API calls. This leads us to use another callback inside the previous, and so on.

This means we get into something we call (very expressively) Callback Hell. And who wants that? It also forms a code pyramid which is not only confusing but also error-prone.

callback-hell

Demonstration of callback hell and pyramid

There are a few ways to come out of (or not get into) callback hell. The most common one is by using a Promise or async function. However, to understand async functions well, you need to have a fair understanding of Promises first.

So let’s get started and dive into promises.

Understanding Promise States

Just to review, a promise can be created with the constructor syntax, like this:

let promise = new Promise(function(resolve, reject) {
  // Code to execute
});

The constructor function takes a function as an argument. This function is called the executor function.

// Executor function passed to the 
// Promise constructor as an argument
function(resolve, reject) {
    // Your logic goes here...
}

The executor function takes two arguments, resolve and reject. These are the callbacks provided by the JavaScript language. Your logic goes inside the executor function that runs automatically when a new Promise is created.

For the promise to be effective, the executor function should call either of the callback functions, resolve or reject. We will learn more about this in detail in a while.

The new Promise() constructor returns a promise object. As the executor function needs to handle async operations, the returned promise object should be capable of informing when the execution has been started, completed (resolved) or retuned with error (rejected).

A promise object has the following internal properties:

  1. state – This property can have the following values:
  • pending: Initially when the executor function starts the execution.
  • fulfilled: When the promise is resolved.
  • rejected: When the promise is rejected.

states_1

Promise states

2.  result – This property can have the following values:

  • undefined: Initially when the state value is pending.
  • value: When resolve(value) is called.
  • error: When reject(error) is called.

These internal properties are code-inaccessible but they are inspectable. This means that we will be able to inspect the state and result property values using the debugger tool, but we will not be able to access them directly using the program.

promise_state_inspect

Able to inspect the internal properties of a promise

A promise’s state can be pending, fulfilled or rejected. A promise that is either resolved or rejected is called settled.

states_2

A settled promise is either fulfilled or rejected

How promises are resolved and rejected

Here is an example of a promise that will be resolved (fulfilled state) with the value I am done immediately.

let promise = new Promise(function(resolve, reject) {
    resolve("I am done");
});

The promise below will be rejected (rejected state) with the error message Something is not right!.

let promise = new Promise(function(resolve, reject) {
    reject(new Error('Something is not right!'));
});

An important point to note:

A Promise executor should call only one resolve or one reject. Once one state is changed (pending => fulfilled or pending => rejected), that’s all. Any further calls to resolve or reject will be ignored.

let promise = new Promise(function(resolve, reject) {
  resolve("I am surely going to get resolved!");

  reject(new Error('Will this be ignored?')); // ignored
  resolve("Ignored?"); // ignored
});

In the example above, only the first one to resolve will be called and the rest will be ignored.

How to handle a Promise once you’ve created it

A Promise uses an executor function to complete a task (mostly asynchronously). A consumer function (that uses an outcome of the promise) should get notified when the executor function is done with either resolving (success) or rejecting (error).

The handler methods, .then(), .catch() and .finally(), help to create the link between the executor and the consumer functions so that they can be in sync when a promise resolves or rejects.

consumer_executor

The executor and consumer functions

How to Use the .then() Promise Handler

The .then() method should be called on the promise object to handle a result (resolve) or an error (reject).

It accepts two functions as parameters. Usually, the .then() method should be called from the consumer function where you would like to know the outcome of a promise’s execution.

promise.then(
  (result) => { 
     console.log(result);
  },
  (error) => { 
     console.log(error);
  }
);

If you are interested only in successful outcomes, you can just pass one argument to it, like this:

promise.then(
  (result) => { 
      console.log(result);
  }
);

If you are interested only in the error outcome, you can pass null for the first argument, like this:

promise.then(
  null,
  (error) => { 
      console.log(error)
  }
);

However, you can handle errors in a better way using the .catch() method that we will see in a minute.

Let’s look at a couple of examples of handling results and errors using the .then and .catch handlers. We will make this learning a bit more fun with a few real asynchronous requests. We will use the PokeAPI to get information about Pokémon and resolve/reject them using Promises.

First, let us create a generic function that accepts a PokeAPI URL as argument and returns a Promise. If the API call is successful, a resolved promise is returned. A rejected promise is returned for any kind of errors.

We will be using this function in several examples from now on to get a promise and work on it.

function getPromise(URL) {
  let promise = new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest();
    req.open("GET", URL);
    req.onload = function () {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject("There is an Error!");
      }
    };
    req.send();
  });
  return promise;
}
Utility method to get a Promise

Example 1: Get 50 Pokémon’s information:

const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50';

// We have discussed this function already!
let promise = getPromise(ALL_POKEMONS_URL);

const consumer = () => {
    promise.then(
        (result) => {
            console.log({result}); // Log the result of 50 Pokemons
        },
        (error) => {
            // As the URL is a valid one, this will not be called.
            console.log('We have encountered an Error!'); // Log an error
    });
}

consumer();

Example 2: Let’s try an invalid URL

const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/';

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.then(
        (result) => {
            // The promise didn't resolve. Hence, it will
            // not be executed.
            console.log({result});
        },
        (error) => {
            // A rejected prmise will execute this
            console.log('We have encountered an Error!'); // Log an error
        }
    );
}

consumer();

How to Use the .catch() Promise Handler

You can use this handler method to handle errors (rejections) from promises. The syntax of passing null as the first argument to the .then() is not a great way to handle errors. So we have .catch() to do the same job with some neat syntax:

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.catch(error => console.log(error));
}

consumer();

If we throw an Error like new Error("Something wrong!")  instead of calling the reject from the promise executor and handlers, it will still be treated as a rejection. It means that this will be caught by the .catch handler method.

This is the same for any synchronous exceptions that happen in the promise executor and handler functions.

Here is an example where it will be treated like a reject and the .catch handler method will be called:

new Promise((resolve, reject) => {
  throw new Error("Something is wrong!");// No reject call
}).catch((error) => console.log(error)); 

How to Use the .finally() Promise Handler

The .finally() handler performs cleanups like stopping a loader, closing a live connection, and so on. The finally() method will be called irrespective of whether a promise resolves or rejects. It passes through the result or error to the next handler which can call a .then() or .catch() again.

Here is an example that’ll help you understand all three methods together:

let loading = true;
loading && console.log('Loading...');

// Gatting Promise
promise = getPromise(ALL_POKEMONS_URL);

promise.finally(() => {
    loading = false;
    console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
    console.log({result});
}).catch((error) => {
    console.log(error)
});

To explain a bit further:

  • The .finally() method makes loading false.
  • If the promise resolves, the .then() method will be called. If the promise rejects with an error, the .catch() method will be called. The .finally() will be called irrespective of the resolve or reject.

What is the Promise Chain?

The  promise.then() call always returns a promise. This promise will have the state as pending and result as undefined. It allows us to call the next .then method on the new promise.

When the first .then method returns a value, the next .then method can receive that. The second one can now pass to the third .then() and so on. This forms a chain of .then methods to pass the promises down. This phenomenon is called the Promise Chain.

image-105

Promise Chain

Here is an example:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
}).catch(error => {
    console.log('In the catch', error);
});

Here we first get a promise resolved and then extract the URL to reach the first Pokémon. We then return that value and it will be passed as a promise to the next .then() handler function. Hence the output,

https://pokeapi.co/api/v2/pokemon/1/

The .then method can return either:

  • A value (we have seen this already)
  • A brand new promise.

It can also throw an error.

Here is an example where we have created a promise chain with the .then methods which returns results and a new promise:

// Promise Chain with multiple then and catch
let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
}).then(pokemon => {
    console.log(JSON.parse(pokemon));
}).catch(error => {
    console.log('In the catch', error);
});

In the first .then call we extract the URL and return it as a value. This URL will be passed to the second .then call where we are returning a new promise taking that URL as an argument.

This promise will be resolved and passed down to the chain where we get the information about the Pokémon. Here is the output:

image-159

Output of the promise chain call

In case there is an error or a promise rejection, the .catch method in the chain will be called.

A point to note: Calling .then multiple times doesn’t form a Promise chain. You may end up doing something like this only to introduce a bug in the code:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
});
promise.then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
});
promise.then(pokemon => {
    console.log(JSON.parse(pokemon));
});

We call the .then method three times on the same promise, but we don’t pass the promise down. This is different than the promise chain. In the above example, the output will be an error.

image-160

How to Handle Multiple Promises

Apart from the handler methods (.then, .catch, and .finally), there are six static methods available in the Promise API. The first four methods accept an array of promises and run them in parallel.

  1. Promise.all
  2. Promise.any
  3. Promise.allSettled
  4. Promise.race
  5. Promise.resolve
  6. Promise.reject

Let’s go through each one.

The Promise.all() method

Promise.all([promises]) accepts a collection (for example, an array) of promises as an argument and executes them in parallel.

This method waits for all the promises to resolve and returns the array of promise results. If any of the promises reject or execute to fail due to an error, all other promise results will be ignored.

Let’s create three promises to get information about three Pokémons.

const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur';
const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate';
const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna';


let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
let promise_2 = getPromise(RATICATE_POKEMONS_URL);
let promise_3 = getPromise(KAKUNA_POKEMONS_URL);

Use the Promise.all() method by passing an array of promises.

Promise.all([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('An Error Occured');
});

Output:

image-161

As you see in the output, the result of all the promises is returned. The time to execute all the promises is equal to the max time the promise takes to run.

The Promise.any() method

Promise.any([promises]) — Similar to the all() method, .any() also accepts an array of promises to execute them in parallel. This method doesn’t wait for all the promises to resolve. It is done when any one of the promises is settled.

 Promise.any([promise_1, promise_2, promise_3]).then(result => {
     console.log(JSON.parse(result));
 }).catch(error => {
     console.log('An Error Occured');
 });

The output would be the result of any of the resolved promises:

image-162

The Promise.allSettled() method

romise.allSettled([promises]) — This method waits for all promises to settle(resolve/reject) and returns their results as an array of objects. The results will contain a state (fulfilled/rejected) and value, if fulfilled. In case of rejected status, it will return a reason for the error.

Here is an example of all fulfilled promises:

Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('There is an Error!');
});

Output:

image-163

If any of the promises rejects, say, the promise_1,

let promise_1 = getPromise(POKEMONS_BAD_URL);

image-164

The Promise.race() method

Promise.race([promises]) – It waits for the first (quickest) promise to settle, and returns the result/error accordingly.

Promise.race([promise_1, promise_2, promise_3]).then(result => {
    console.log(JSON.parse(result));
}).catch(error => {
    console.log('An Error Occured');
});

Output the fastest promise that got resolved:

image-165

The Promise.resolve/reject methods

Promise.resolve(value) – It resolves a promise with the value passed to it. It is the same as the following:

let promise = new Promise(resolve => resolve(value));

Promise.reject(error) – It rejects a promise with the error passed to it. It is the same as the following:

let promise = new Promise((resolve, reject) => reject(error));

Can we rewrite the PizzaHub example with Promises?

Sure, let’s do it. Let us assume that the query method will return a promise. Here is an example query() method. In real life, this method may talk to a database and return results. In this case, it is very much hard-coded but serves the same purpose.

function query(endpoint) {
  if (endpoint === `/api/pizzahub/`) {
    return new Promise((resolve, reject) => {
      resolve({'shopId': '123'});
    })
  } else if (endpoint.indexOf('/api/pizzahub/pizza/') >=0) {
    return new Promise((resolve, reject) => {
      resolve({pizzas: [{'type': 'veg', 'name': 'margherita', 'id': '123'}]});
    })
  } else if (endpoint.indexOf('/api/pizzahub/beverages') >=0) {
    return new Promise((resolve, reject) => {
      resolve({id: '10', 'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  } else if (endpoint === `/api/order`) {
    return new Promise((resolve, reject) => {
      resolve({'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  }
}

Next is the refactoring of our callback hell. To do that, first, we will create a few logical functions:

// Returns a shop id
let getShopId = result => result.shopId;

// Returns a promise with pizza list for a shop
let getPizzaList = shopId => {
  const url = `/api/pizzahub/pizza/${shopId}`;
  return query(url);
}

// Returns a promise with pizza that matches the customer request
let getMyPizza = (result, type, name) => {
  let pizzas = result.pizzas;
  let myPizza = pizzas.find((pizza) => {
    return (pizza.type===type && pizza.name===name);
  });
  const url = `/api/pizzahub/beverages/${myPizza.id}`;
  return query(url);
}

// Returns a promise after Placing the order
let performOrder = result => {
  let beverage = result.id;
   return query(`/api/order`, {'type': result.type, 'name': result.name, 'beverage': result.beverage});
}

// Confirm the order
let confirmOrder = result => {
    console.log(`Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!`);
}

Use these functions to create the required promises. This is where you should compare with the callback hell example. This is so nice and elegant.

function orderPizza(type, name) {
  query(`/api/pizzahub/`)
  .then(result => getShopId(result))
  .then(shopId => getPizzaList(shopId))
  .then(result => getMyPizza(result, type, name))
  .then(result => performOrder(result))
  .then(result => confirmOrder(result))
  .catch(function(error){
    console.log(`Bad luck, No Pizza for you today!`);
  })
}

Finally, call the orderPizza() method by passing the pizza type and name, like this:

orderPizza('veg', 'margherita');

What’s next from here?

If you are here and have read through most of the lines above, congratulations! You should now have a better grip of JavaScript Promises. All the examples used in this article are in this GitHub repository.

Next, you should learn about the async function in JavaScript which simplifies things further. The concept of JavaScript promises is best learned by writing small examples and building on top of them.

Irrespective of the framework or library (Angular, React, Vue, and so on) we use, async operations are unavoidable. This means that we have to understand promises to make things work better.

Also, I’m sure you will find the usage of the fetch method much easier now:

fetch('/api/user.json')
.then(function(response) {
    return response.json();
})
.then(function(json) {
    console.log(json); // {"name": "tapas", "blog": "freeCodeCamp"}
});
  • The fetch method returns a promise. So we can call the .then handler method on it.
  • The rest is about the promise chain which we learned in this article.

Before we end…

Thank you for reading this far! Let’s connect. You can @ me on Twitter (@tapasadhikary) with comments.

You may also like these other articles:

  • JavaScript undefined and null: Let’s talk about it one last time!
  • JavaScript: Equality comparison with ==, === and Object.is
  • The JavaScript `this` Keyword + 5 Key Binding Rules Explained for JS Beginners
  • JavaScript TypeOf – How to Check the Type of a Variable or Object in JS

That’s all for now. See you again with my next article soon. Until then, please take good care of yourself.



Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

Понравилась статья? Поделить с друзьями:
  • Promise reject throw error
  • Promise error handling
  • Promise all catch error
  • Prototype 2 mainwin32 error
  • Prototype 2 error could not load prototype2engine dll