В предыдущей части становится понятно, что использовать функции обратного вызова (callback) для обработки асинхронного кода не очень-то эффективно. В ES6 была добавлена концепция обещаний (promises), которые будут рассмотрены далее.
Обещания (Promises)
Обещание (promise) представляет собой завершение асинхронной функции. Это объект, который может вернуть значение когда-нибудь в будущем. Он выполняет ту же миссию, что функция обратного вызова (callback), но с множеством дополнительных фич и более читаемым синтаксисом.
Создание обещания (Promise)
Promise инициализируется с помощью new Promise(fn), и ему следует передавать функцию. Функция, которая передается в обещание, имеет параметры для разрешения (resolve) и отклонения (reject). Функции resolve и reject обрабатывают, соответственно, успех и неудачу операции. Пример:
// Инициализация Promise
const promise = new Promise((resolve, reject) => {})
Если проверить инициализированный promise в этом состоянии с помощью консоли веб-браузера, можно обнаружить, что оно имеет статус pending и значение undefined:
__proto__: Promise
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined
Пока для promise ничего не настроено, оно будет оставаться в состоянии pending. Первое, что уже можно сделать, чтобы проверить promise – это выполнить promise, разрешив его с каким-нибудь значением:
const promise = new Promise((resolve, reject) => {
resolve('Мы сделали это!')
})
Теперь, после проверки, можно обнаружить, что статус promise изменился на fulfilled, а у value значению, которое было передано в функцию resolve:
__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: "Мы сделали это!"
Как указано в начале этого раздела, promise – это объект, который может возвращать значение. После успешного выполнения содержимое value меняется с undefined на заполненное данными. У обещания бывает три возможных состояния: отложено (pending), выполнено (fulfilled) и отклонено (rejected).
Pending– исходное состояние до разрешения или отклоненияFulfilled– успешная операция, обещание разрешеноRejected– неудачная операция, обещание отклонено
После выполнения или отклонения обещание считается выполненным. Теперь, когда у есть представление о том, как создаются promise, посмотрим, как их можно использовать.
Использование обещания (Promise)
Promise в последнем примере выполнено со значением, поэтому можно получить доступ к этому значению. У обещаний есть метод then, который выполнится после того, как в коде оно доберётся до resolve.then вернёт значение promise в качестве параметра. Вот пример, как это можно сделать:
promise.then(response => {
console.log(response)
})
Значением [[PromiseValue]] из примера выше было “Мы сделали это!”. Это значение будет передано анонимной функции в качестве response:
Мы сделали это!
До сих пор этот пример не включал асинхронный веб-API и только объяснял, как создавать, обрабатывать и использовать встроенное обещание (promise). Используя setTimeout, можно имитировать асинхронный запрос и посмотреть, как это работает:
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Разрешение асинхронного запроса!'), 2000)
})
// Результат
promise.then(response => {
console.log(response)
})
Использование синтаксиса then гарантирует, что response попадёт в консоль только через 2000 миллисекунд, когда операция setTimeout будет завершена. Все это делается без вложенных обратных вызовов (callback).
Теперь, через две секунды, разрешится значение promise и then напишет в консоль
Разрешение асинхронного запроса!
Обещания также могут быть связаны в цепочку для передачи данных нескольких асинхронных операций. Каждый then в этой цепочке может изменять принимаемое значение и передавать его дальше в следующий then.
// promise-цепочка
promise
.then(firstResponse => {
// возвращает новое значение для следующего then
return firstResponse + ' И цепочка!'
})
.then(secondResponse => {
console.log(secondResponse)
})
Выполненное во втором then обещание напишет в консоль:/p>
Разрешение асинхронного запроса! И цепочка!
Выстраивание then в цепочку, делает внешний вид кода для promise более читаемым, чем у вложенных обратных вызовов, его будет проще поддерживать и проверять.
Обработка ошибок
До сего времени рассматривались примеры promise только с успешным разрешением resolve, которое переводит обещание в состояние fullfilled (выполнено). Но часто с асинхронным запросом необходимо обрабатывать ошибки – если API не работает, отправляется неверный или неавторизованный запрос. Promise должен обрабатывать оба случая.
В этом примере в функцию getUsers передается флаг, а она возвращает promise.
function getUsers(onSuccess) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// имитация разрешения и отклонения в асинхронном API
}, 1000)
})
}
Теперь дополняем код так, чтобы, когда в onSuccess передано true, таймаут выполнялся с некоторыми данными. Если false, promise будет отклонен с ошибкой.
function getUsers(onSuccess) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// имитация разрешения и отклонения в асинхронном API
if (onSuccess) {
resolve([
{ id: 1, name: 'Пётр' },
{ id: 2, name: 'Фёдор' },
{ id: 3, name: 'Егор' },
])
} else {
reject('Ошибка получения данных!')
}
}, 1000)
})
}
Для успешного результата возвращается JavaScript-объект, который имитирует список пользователей.
Для того, чтобы обработать ошибку, следует использовать метод catch. Он возвращает ошибку, которую можно обработать в передаваемом ему в качестве параметра обратном вызове.
Для примера, вызываем функцию getUser с параметром onSuccess, установленным в false, используем метод then для обработки успеха и метод catch для ошибки:
// чтобы имитировать ошибку,
// надо запустить функции getUsers с флагом false
getUsers(false)
.then(response => {
console.log(response)
})
.catch(error => {
console.error(error)
})
Поскольку promise был отклонен и имитирована ошибка, then будет пропущен, а ошибка будет обработана в catch:
Ошибка получения данных!
Если флаг, передаваемый в getUser переключить в true, promise будет разрешено успешно, выполнится код внутри then, а catch игнорироваться.
// чтобы имитировать успех,
// надо запустить функции getUsers с флагом true
getUsers(true)
.then(response => {
console.log(response)
})
.catch(error => {
console.error(error)
})
B будет имитироваться асинхронное получение списка пользователей:
(3) [{…}, {…}, {…}]
0: {id: 1, name: "Пётр"}
1: {id: 2, name: "Фёдор"}
3: {id: 3, name: "Егор"}
Подведём итог:
then()- Обрабатывает
resolve. Возвращает promise и асинхронно вызывает функциюonFulfilled. catch()- Обрабатывает
reject. Возвращает promise и асинхронно вызывает функциюonRejected. finally()- Вызывается, когда обещание выполнено. Возвращает promise и вызывает асинхронную функцию
onFinally.
Promise могут сбивать с толку как новичков, так и опытных программистов, которые никогда раньше не работали в асинхронной среде. Однако, как уже упоминалось, гораздо чаще приходится использовать promise, чем создавать их. Обычно веб-API браузера или сторонняя библиотека предоставляют promise, и остается только использовать его.
В последнем разделе о promise в этом руководстве будет приведен типичный пример использования веб-API, который возвращает обещания: Fetch API.
Использование Fetch API с promise
Одним из наиболее полезных и часто используемых веб-API, возвращающих promise, является Fetch API, который позволяет выполнять асинхронные сетевые запросы. fetch – это процесс, состоящий из двух частей, поэтому он требует цепочки then. В этом примере демонстрируется использование API GitHub для получения данных пользователя, а также обработка любой потенциальной ошибки:
// Получить пользователя из API GitHub
fetch('https://api.github.com/users/octocat')
.then(response => {
return response.json()
})
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
Асинхронный запрос методом fetch отправляется на URL https://api.github.com/users/octocat и ожидает получения ответа. Первый then передает ответ анонимной функции, которая отформатирует ответ в виде JSON, затем передает этот JSON второму then, который отправляет данные в консоль. Оператор catch записывает любую ошибку в консоль. Выполнение этого кода возвращает следующее:
login: "octocat",
id: 583231,
avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4"
blog: "https://github.blog"
company: "@github"
followers: 3203
...
Это данные, запрошенные с https://api.github.com/users/octocat, представленные в формате JSON.
В этом разделе руководства показано, что promise включают в себя множество улучшений для работы с асинхронным кодом. Хотя использование then для обработки асинхронных действий легче, чем пирамида обратных вызовов (callback), некоторые разработчики по-прежнему предпочитают синхронный формат написания асинхронного кода. Чтобы удовлетворить эту потребность и упростить работу с обещаниями, ECMAScript 2016 (ES7) предлагает функцию async и ключевое слово await. В следующей части рассматривается использование async/await.


