Обзор Async Await

Обзор Async Await

От автора: при написании кода для Веб, в конечном итоге вам понадобится выполнить некоторый процесс, который может занять несколько минут. 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 &#x1f32e;s
> Here are your &#x1f32e;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.