От автора: при написании кода для Веб, в конечном итоге вам понадобится выполнить некоторый процесс, который может занять несколько минут. JavaScript не может работать в многозадачном режиме, поэтому нам понадобится способ справиться с этими длительными процессами.
Async / Await — это способ обработки такого типа временной последовательности. Это особенно удобно, когда вам нужно выполнить какой-то сетевой запрос и затем работать с полученными данными. Давайте разберемся с этим!
Промис? Промис.
Async / Await — это тип промиса. Промисы в JavaScript — это объекты, которые могут иметь несколько состояний (вроде реальных). Промисы ведут себя так, потому что иногда то, что мы просим, не доступно сразу, и мы должны быть в состоянии определить, в каком состоянии он находится.
Представьте, что кто-то просит вас пообещать сделать что-то для них, например, помочь им двигаться. Есть начальное состояние, где они просят помочь. Но вы не выполните свое обещание, пока не придете к ним и не поможете им двигаться. Если вы отмените свои планы, вы отклонили обещание.
Точно так же три возможных состояния для промиса в JavaScript:
в ожидании: когда вы впервые вызвали промис, и неизвестно, что он вернет
выполнено: означает, что операция успешно завершена
отклонено: операция не удалась
Вот пример промисов в этих состояниях. Здесь выполненное состояние. Мы сохраняем промис getSomeTacos, переданный в параметрах разрешения и отклонения. Мы говорим, что промис разрешен, и это позволяет затем вывести console log еще два раза.
const getSomeTacos = new Promise((resolve, reject) => { console.log("Initial state: Excuse me can I have some tacos"); resolve(); }) .then(() => { console.log("Order some tacos"); }) .then(() => { console.log("Here are your tacos"); }) .catch(err => { console.error("Nope! No tacos for you."); }); > Initial state: Excuse me can I have some tacos > Order some tacos > Here are your tacos
Если мы выберем отклоненное состояние, мы выполним ту же функцию, но на этот раз отклоним ее. Теперь то, что будет выведено на консоль, это начальное состояние и ошибка catch:
const getSomeTacos = new Promise((resolve, reject) => { console.log("Initial state: Excuse me can I have some tacos"); reject(); }) .then(() => { console.log("Order some tacos"); }) .then(() => { console.log("Here are your tacos"); }) .catch(err => { console.error("Nope! No tacos for you."); }); > Initial state: Excuse me can I have some tacos > Nope! No tacos for you.
И когда мы выбираем состояние ожидания, мы просто выводим на консоль то, что сохранили в getSomeTacos. Это выведет состояние ожидания, потому что это состояние, в котором находится промис, когда мы его регистрируем!
console.log(getSomeTacos) > Initial state: Excuse me can I have some 🌮s > Promise {<pending>} > Order some 🌮s > Here are your 🌮s
Что дальше?
Но вот часть, которая сначала смутила меня. Чтобы получить значение из промиса, вы должны использовать .then() или что-то, что возвращает разрешение промиса. Это имеет смысл, если вы представите, что вам нужно определить, каким он будет в конечном итоге — а не тем, чем оно изначально является, — потому что изначально он будет находиться в состоянии ожидания. Вот почему мы увидели его вывод, как Promise{<pending>}, когда записали промис выше. На этом этапе еще ничего не решено.
Async / Await – это на самом деле синтаксический сахар в дополнение к тем промисам, которые вы только что видели. Вот небольшой пример того, как я могла бы использовать его вместе с промисами, чтобы запланировать несколько выполнений.
async function tacos() { return await Promise.resolve("Now and then I get to eat delicious tacos!") }; tacos().then(console.log)
Или более подробный пример:
// this is the function we want to schedule. it's a promise. const addOne = (x) => { return new Promise(resolve => { setTimeout(() => { console.log(`I added one! Now it's ${x + 1}.`) resolve() }, 2000); }) } // we will immediately log the first one, // then the addOne promise will run, taking 2 seconds // then the final console.log will fire async function addAsync() { console.log('I have 10') await addOne(10) console.log(`Now I'm done!`) } addAsync() > I have 10 > I added one! Now it's 11. > Now I'm done!
Один ждет другого
Одним из распространенных применений Async / Await является использование ее для объединения нескольких асинхронных вызовов. Здесь мы извлечем некоторый JSON, который будем использовать для перехода к следующему вызову fetch, чтобы выяснить, какой тип данных мы хотим извлечь из второго API. В нашем случае мы хотим получить доступ к некоторым шуткам программистов, но сначала нам нужно выяснить из другого API, какой тип цитаты нам нужен.
Первый файл JSON выглядит так: мы хотим, чтобы тип цитаты был случайным:
{ "type": "random" }
Второй API вернет что-то похожее на это, учитывая тот параметр запроса random, который мы только что получили:
{ "_id":"5a933f6f8e7b510004cba4c2", "en":"For all its power, the computer is a harsh taskmaster. Its programs must be correct, and what we wish to say must be said accurately in every detail.", "author":"Alan Perlis", "id":"5a933f6f8e7b510004cba4c2" }
Мы вызываем функцию async, затем даем ей подождать, чтобы получить первый .json файл, прежде чем она получит данные из API. Как только это произойдет, мы можем что-то сделать с этим ответом, например, добавить его на страницу.
async function getQuote() { // get the type of quote from one fetch call, everything else waits for this to finish let quoteTypeResponse = await fetch(`https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/quotes.json`) let quoteType = await quoteTypeResponse.json() // use what we got from the first call in the second call to an API, everything else waits for this to finish let quoteResponse = await fetch("https://programming-quotes-api.herokuapp.com/quotes/" + quoteType.type) let quote = await quoteResponse.json() // finish up console.log('done') }
Мы даже можем упростить это, используя литералы шаблонов и стрелочные функции:
async function getQuote() { // get the type of quote from one fetch call, everything else waits for this to finish let quoteType = await fetch(`quotes.json`).then(res => res.json()) // use what we got from the first call in the second call to an API, everything else waits for this to finish let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json()) // finish up console.log('done') } getQuote()
Вот анимированная иллюстрация этого процесса:
Try, catch и finally
В конце концов, нам нужно добавить перехват ошибок в этот процесс. У нас есть под рукой блоки try, catch и finally.
try { // I’ll try to execute some code for you } catch(error) { // I’ll handle any errors in that process } finally { // I’ll fire either way }
Давайте реструктурируем приведенный выше код, чтобы использовать этот синтаксис для перехвата ошибок.
async function getQuote() { try { // get the type of quote from one fetch call, everything else waits for this to finish let quoteType = await fetch(`quotes.json`).then(res => res.json()) // use what we got from the first call in the second call to an API, everything else waits for this to finish let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json()) // finish up console.log('done') } catch(error) { console.warn(`We have an error here: ${error}`) } } getQuote()
Мы не использовали здесь finally, потому что это не всегда нужно. Это блок, который всегда срабатывает независимо от того, успешно ли выполнение или нет. Попробуйте использовать finally в любое время, когда вы дублируете вещи в обоих try и catch. Я обычно использую его для некоторой очистки.
Возможно, вам понадобится более сложная обработка ошибок, например, способ отменить асинхронную функцию. К сожалению, нет способа сделать это нативно, но, к счастью, Кайл Симпсон создал библиотеку под названием CAF, которая может помочь.
Автор: Sarah Drasner
Источник: https://css-tricks.com
Редакция: Команда webformyself.