От автора: скорее всего, вы сталкивались с промисами в своем JavaScript. Они позволяют подключиться к завершению асинхронных вызовов. Они упрощают цепочку асинхронных операций или даже группируют их вместе. Есть один крошечный недостаток. При использовании промисов синтаксис не всегда самый красивый. Представляем async + await.
Для тех, кто в лагере TL; DR, async + await являются синтаксическим сахаром для применения промисов. Они помогают понять поток кода. Они не вводят никаких новых понятий.
Выпечка пирога из кода
Мы собираемся испечь пирог! Чтобы испечь пирог, нам сначала нужно получить ингредиенты. Извините, это простой рецепт: масло, мука, сахар, яйца.
В нашем коде получение каждого ингредиента требует асинхронной операции. Например, вот метод getButter:
1 2 3 |
const getButter = () => new Promise((resolve, reject) => { setTimeout(() => resolve('Butter'), 3000) }) |
Эти операции станут частью метода getIngredients. Когда мы печем торт, нам нужно вызвать getIngredients перед тем, как смешать его и т. д.
С помощью промисов
Давайте предположим, что нам нужно связать каждую асинхронную операцию. getIngredients — это прогулка по супермаркету, когда мы собираем ингредиенты один за другим.
В большинстве случаев нам нужно объединять операции только в том случае, если они зависят друг от друга. Например, если вторая операция требует возврата значения из первой и так далее.
В нашем примере, возможно, мы можем добавлять в корзину только один товар за один раз. Это означает, что нам нужно продвигаться по ингредиентам один за другим. Помните, что код здесь является гипотетическим и предназначен для демонстрации promise.
Как может getIngredients выглядеть с промисами? Я, конечно, видел подобные вложенные промисы раньше.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const getIngredients = () => new Promise((resolve, reject) => { getButter().then((butter) => { updateBasket(butter) getFlour().then((flour) => { updateBasket(flour) getSugar().then((sugar) => { updateBasket(sugar) getEggs().then((eggs) => { updateBasket(eggs) resolve(basket) }) }) }) }) }) |
Это работает, но выглядит не очень хорошо. Это выглядело бы лучше с цепочкой промисов.
1 2 3 4 5 6 7 8 |
const getIngredients = () => getButter() .then(updateBasket) .then(getFlour) .then(updateBasket) .then(getSugar) .then(updateBasket) .then(getEggs) .then(updateBasket) |
Если бы мы делали покупки в Интернете, мы могли бы использовать Promise.all.
1 2 3 4 5 6 |
const getIngredients = () => Promise.all([ getButter(), getFlour(), getSugar(), getEggs(), ]) |
Они выглядят намного аккуратнее, но нам все еще нужно использовать обратный вызов, чтобы получить эти ингредиенты.
1 |
getIngredients().then(ingredients => doSomethingWithIngredients(ingredients)) |
Использование async + await
Давайте добавим синтаксический сахар и используем ключевое слово await, мы должны сначала объявить метод как асинхронный с помощью ключевого слова async. Важно отметить, что асинхронный метод всегда возвращает Promise. Это означает, что нет необходимости явно возвращать Promise.
Давайте объявим getIngredients, как async:
1 |
const getIngredients = async () => {} |
Теперь, как эти промисы могут выглядеть с сахаром? Ключевое слово await позволяет ожидать промис и определять переменную с помощью возвращаемого значения этого промиса. Это немного многословно, но давайте применим этот сахар к getIngredients.
1 2 3 4 5 6 7 8 9 10 11 12 |
const getIngredients = async () => { const butter = await getButter() const flour = await getFlour() const sugar = await getSugar() const eggs = await getEggs() return [ butter, flour, sugar, eggs, ] } |
Код не стал меньше, но он более лаконичен. Больше никаких обратных вызовов. Здесь в игру вступает синтаксический сахар.
1 2 3 4 |
const bakeACake = async () => { const ingredients = await getIngredients() // do something with the ingredients, no more ".then" } |
Вот это да! Намного чище?
Использование async и await делает наш код всеобъемлющим. Он выглядит чище, а делает то же самое. Здесь важно помнить, что мы не заменяем промисы, мы все еще используем их под капотом. Теперь мы используем их с новым более чистым синтаксисом.
И да, Promise.all тоже работает. Так что, если бы мы делали покупки онлайн, наш код стал бы еще меньше.
1 2 3 4 5 6 7 8 9 |
const getIngredients = async () => { const ingredients = await Promise.all([ getButter(), getFlour(), getSugar(), getEggs(), ]) return ingredients } |
Нам больше не нужна эта функция-оболочка!
1 2 |
const getIngredients = async () => await Promise.all([getButter(), getFlour(), getSugar(), getEggs()]); |
Ожидание не промиса
Как насчет того, если значение, которое вы ожидаете не является Promise? В нашем примере асинхронные функции возвращают String после setTimeout.
1 |
const egg = await |
Ошибки не будет, значение становится разрешенным промисом.
Как насчет отклонений?
До сих пор мы имели дело с успешным выполнением. Но как насчет случая, когда Promise отклоняется? Например, что, если на складе нет яиц? Наша функция async для getEggs будет отклонена с потенциальной ошибкой. В этом случае простой оператор try/ catch выполнит всю работу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const getIngredients = async () => { try { const butter = await 'Butter' const flour = await getFlour() const sugar = await getSugar() const eggs = await getEggs() return [ butter, flour, sugar, eggs, ] } catch(e) { return e } } |
Мы могли бы обернуть в него код на этом уровне или выше, где мы вызываем getIngredients.
Применение нашей функции и выпечка пирога
Если вы дошли до сих пор, то мы создали функцию getIngredients с новыми ключевыми словами async+ await. Как может выглядеть все остальное?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const bakeACake = async () => { try { // get the ingredients const ingredients = await getIngredients() // mix them together const cakeMix = await mix(ingredients) // put in oven on 180C, gas mark 4for 20-25 minutes const hotCake = await cook(cakeMix) // allow to stand before serving const cake = await stand(hotCake) return cake } catch (e) { return e } } |
Намного чище, чем то, что мы могли бы сделать ранее с помощью Promises.
Это все! Выпечка пирога с помощью async+ await завершена
Если вы дошли до этого места, спасибо за чтение. Я подытожил суть с некоторыми возможными примерами кода, которые можно увидеть ниже вместе с некоторыми дополнительными ресурсами по async+ await. Важные моменты:
Функции async всегда возвращает Promise
await в большинстве случаев лучше использовать вместо Promise или группы Promise
Обрабатывайте любые потенциальные ошибки с помощью оператора try/catch
vМы не касались этого, но вы можете заняться. Создавая запрос fetch, вы можете ожидать запрос, а затем ожидать функцию json.
1 |
const data = await (await fetch(`${dataUrl}`)).json() |
Как всегда, любые вопросы или предложения, пожалуйста, оставляйте в комментариях.
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
const PROB = 0.2 const grabIngredient = ingredient => () => { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > PROB) { resolve(ingredient) } else { reject(`Sorry, we've got no ${ingredient}`) } }, Math.random() * 1000) }) } // boilerplate functions for getting the different ingredients const getButter = grabIngredient('Butter') const getFlour = grabIngredient('Flour') const getSugar = grabIngredient('Sugar') const getEggs = grabIngredient('Eggs') const getIngredientsFromTheSuperMarket = async () => { try { const butter = await getButter() const flour = await getFlour() const sugar = await getSugar() const eggs = await getEggs() return [ butter, flour, sugar, eggs, ] } catch(e) { return e } } const getIngredientsOnline = async () => await Promise.all([ getButter(), getFlour(), getSugar(), getEggs(), ]) // boilerplate async functions that return strings const mix = async (ingredients) => `Mixing ${ingredients}` const cook = async (cakeMix) => 'Hot Cake' const stand = async (hotCake) => 'Cake' const bakeACake = async () => { try { const ingredients = await getIngredientsOnline() const cakeMix = await mix(ingredients) const hotCake = await cook(cakeMix) const cake = await stand(hotCake) console.info('BAKED', cake) return cake } catch (e) { console.info(e) } } bakeACake() |
Автор: Jhey Tompkins
Источник: //dev.to
Редакция: Команда webformyself.