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, который выполнится или будет отклонен с результатом исполнения первого выполненного или отклонённого итерируемого промиса.