Promise (промисы) - удобный механизм организации асинхронного кода.
Предположим нам необходимо в JavaScript коде совершить обращение к серверу и с полученными результатами обратиться к серверу еще раз и получить новую информацию, а с этой информацией еще раз. И все эти запросы к серверу мы хотим сделать асинхронными.
Чтобы нам понадобилось при этом. Несколько вложенных двуг в друга callback-функций (обработчиков). Такой код стал бы плохочитаемым. Так же, на бы хотелось иметь обработку ошибок. Для этого пришлось бы добавлять callback-функции и для ошибочных ситуаций. Для решения этой проблемы и существуют промисы.
С помощью них мы можем выстаивать цепочками асинхронные вызовы. Тоесть работать с ними как с синхронным кодом.
Демонстрация простейшего promise:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Определение объекта 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, которая делает асинхронный запрос к серверу и возвращает промис. Мы хотим обратиться к серверу за списком товаров, затем получить дополнительные данные по первому товару, а в итоге отобразить фотографию товара. Если коротко изобразить, то такой код с несколькими асинхронными запросами будет иметь вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Запрос к серверу 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); }); |
Тоесть структура подобных операций такая:
1 2 3 4 |
httpGet(...) .then(...) .then(...) .then(...) |
Правда могут быть и вызовы catch(). Но это просто алиасы к then(null, ...).
Как мы видим, данные после одного асинхронного запроса используются в другом, затем в третьем и так далее.
Но что в случае, если произошла ошибка? В таком случае происходит переход к ближайшему обработчику состояния rejected. Причем он может быть задан как вторым параметром then(), так и вызовом catch(). А в этом обработчике, если мы просто вернем какое-либо значение через return, то управление передет к ближайшему then(). Если же мы вместо этого выбросим исключение через throw, то ошибка переходит в следующий ближайший обработчик rejected.
Кроме then() и catch() есть еще кое-какие полезные методы: