Promise

Promise (промисы) – удобный механизм организации асинхронного кода.

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

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

С помощью них мы можем выстаивать цепочками асинхронные вызовы. Тоесть работать с ними как с синхронным кодом.

Демонстрация простейшего promise:

// Определение объекта promise
let promise = new Promise((resolve, reject) => {
	setTimeout(() => { resolve("Work is finished")  }, 100);
	setTimeout(() => { reject(new Error("Time is up")) }, 1000);
});

// С помощью promise.then() добавляем обработчики
promise
	.then(
	result => {
		// Запустится при вызове resolve из промиса
		// result - аргумент функции resolve
		console.log("Success: " + result); 
	},
	error => {
		// Запустится при вызове reject
		// error - аргумент функции reject
		console.log("Fail: " + error);
	}
);

Здесь роль асинхронного кода выполняет setTimeout(). В консоли выведется строка “Work is finished”, так как этот вариант сработает быстрее (имеет меньшую задержку). Вариант с выводом строки “Time is up” не выведется вообще, так как промис имеет таое свойтсво как состояние. Есть три состояния: ожидание (pending), выполнено успешно (fulfilled), завершено с ошибкой (rejected). При помощи вызова resolve() мы переводим промис в успешное состояние. И вызовом reject() – в состояние ошибки. Из состояния ожидания (в котором промис находится сразу) промис может перейти либо в состояние успеха либо в состояние ошибки. Что произошло ранее, то и будет конечным состоянием. Таким образом, в примере выше мы можем управлять тем, что в итоге выведется в консоли, путем регулировки времени срабатывания.

Как мы видим с помощью then() мы задаем сразу и обработку для успешного срабатывания (первым параметром) и для неудачи (вторым параметром). Но бывает, что нам нужно обработать только неудачу. И для этого у можно в then() предать первым параметром null либо использовать вместо then() вызов catch(), который принимает лишь один агрумент.

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

// Запрос к серверу
httpQuery('/shop/store')
// Получаем данные о товарах в JSON и передаем по цепочке дальше
.then(response => {
	console.log(response);
	return JSON.parse(response);
})
// Получаем информацию о первом товаре
.then(goods => {
	console.log(goods);
	let item = goods[0];
	return httpQuery(`/shop/info/${item.id}`);
})
// Выводим изображение товара
.then(info => {
	console.log(info);

	let img = new Image();
	img.src = JSON.parse(info).picture_url;
	document.body.appendChild(img);

	setTimeout(() => console.log('Last process is done'), 3000);
})
.catch(error => {
    alert(error);
});

Тоесть структура подобных операций такая:

httpGet(...)
  .then(...)
  .then(...)
  .then(...)

Правда могут быть и вызовы catch(). Но это просто алиасы к then(null, …).

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

Но что в случае, если произошла ошибка? В таком случае происходит переход к ближайшему обработчику состояния rejected. Причем он может быть задан как вторым параметром then(), так и вызовом catch(). А в этом обработчике, если мы просто вернем какое-либо значение через return, то управление передет к ближайшему then(). Если же мы вместо этого выбросим исключение через throw, то ошибка переходит в следующий ближайший обработчик rejected.

Кроме then() и catch() есть еще кое-какие полезные методы:

  • Promise.all(iterable) – возвращает promise, который выполнится после выполнения всех промисов в передаваемом итерируемом (например массив) аргументе.
  • Promise.race(iterable) – возвращает promise, который выполнится или будет отклонен с результатом исполнения первого выполненного или отклонённого итерируемого промиса.

Оставить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *