(Примечание: обещание здесь относится к цепочке обещаний)

Попробуйте погуглить «смешайте обещание и ожидание», первые два результата говорят вам не делать этого.

Первый результат — фактически автор забыл дождаться своего обещания.

Та же ошибка может произойти, если он перейдет на чистое асинхронное ожидание (без использования каких-либо `.then` и .catch`) и забудет дождаться своего промиса.

Неважно, смешивает ли он Promise и Async Await или нет. Если он СНОВА не дождется своего Обещания, ему пиздец.

2-й результат был потому, что ОП забыл вернуть свое обещание.

Неважно, смешивает ли он Promise и Async Await или нет. Если он СНОВА не вернет свое Обещание, ему конец.

Люди быстро приходят к выводу, что «это потому, что вы смешали промис или асинхронное ожидание», но игнорируют тот факт, что они повторят ту же ошибку с чистым стилем промиса или чистого асинхронного ожидания.

Несогласованность кода…?

Я понимаю, людям нравится хранить синий мяч в синей коробке. Я чувствую, как моя кровь закипает, когда я вижу, как кто-то выбрасывает пищевые отходы в мусорную корзину, предназначенную для пластика.

Но можем ли мы остановиться и подумать, что на самом деле делает код непоследовательным?

можете ли вы написать согласованный код Promise?
можете ли вы написать согласованный код Async Await?
можете ли вы написать согласованный код Promise + Async Await?

Ответ 100% да

можете ли вы написать несогласованный код Promise?
можете ли вы написать несогласованный код Async Await?
можете ли вы написать несогласованный код Promise + Async Await?

ответ тоже 100% да

Ключ к написанию согласованного кода заключается не только в том, **какие** языковые API использовать, но и в том, **как** мы используем языковые API.

Мы упрощаем несогласованность кода, если думаем, что наш код несовместим только потому, что мы используем API двух языков, которые делают одно и то же.

В этой статье я собираюсь показать вам не только то, что написать согласованный код Promise + Async Await проще, чем написать согласованный код Promise или согласованный код Async Await, я также покажу вам, почему код Promise + Async Await легче понять и может привести к ошибкам. к лучшей согласованности кода.

Ады

Чтобы написать хороший код Promise + Async Await, нам нужно посмотреть, как стили API обоих языков влияют на нашу структуру кода, и понять сильные и слабые стороны каждого подхода.

С точки зрения функциональности Promise и Async Await делают одно и то же. У них схожая сложность, схожая читабельность и концептуально схожие ады.

Я описываю Promise как **горизонтальный**, потому что Promise имеет горизонтальную читабельность и страдает от горизонтального ада.

Я описываю Async Await как **вертикальный**, потому что Async Await имеет вертикальную читабельность и страдает от вертикального ада.

И Promise, и Async Await страдают от одного общего ада: ада вложенности.

Здесь мы собираемся продемонстрировать все ады на общем примере, условия таковы:

1. Нам нужно сделать 3 исходящих вызова API, последние вызовы требуют данных из предыдущего вызова.
2. Нам нужно обработать каждую ошибку вызова API.

Ад обещаний

Давайте решим проблему с Promise и пройдемся по вариантам, которые у нас есть.

Затем мы анализируем, что мешает им работать.

Обратный ад

fetchOne()
 .then(data => {
      return fetchTwo(data)
       .then(data => {
          return fetchThree(data)
           .then(data => {
            // handle data
           })
           .catch(err => {
            // handle error
           })
       })
     .catch(err => {
      // handle error
     })
 })
 .catch(err => {
  // handle error
 })

Это вложенный ад Promise, также известный как Callback Hell.

Вам это не нравится? Хорошо, попробуй Bridge Hell

Мост Ада

fetchOne()
 .catch(err => {
  // handle error
 })
 .then(data => {
    return fetchTwo(data).catch(err => {
     // handle error
    })
 })
 .then(data => {
    if (data) {
     return fetchThree(data).catch(err => {
      // handle error
     })
  }
 })
 .then(data => {
    if (data) {
     // handle data
    }
 })

Представляя горизонтальный ад Promise, я называю его Bridge Hell.

Мы можем читать все слева направо.

Кроме того, что он немного сбивает с толку и отличается от того, как мы обычно читаем код (сверху вниз), у него нет серьезных недостатков.

Но некоторым программистам может быть трудно обосновать это.

Ады Async Await

Многие люди считают, что Async Await — это решение ада Promise. Это связано с тем, что большинство программистов начинают свой путь с процедурного программирования, это наша привычка, и мы очень любим свои привычки.

Но если присмотреться, то Async Await ничего не решает, у него та же проблема с Promise, но перевёрнутая на 180 градусов.

Мы просто прыгаем из ада «мне это не нравится» в ад «мне это нравится».

Сфера Ад

const abc = async () => {
   try {
    const data = await fetchOne()
      try {
         const data2 = await fetchTwo(data)
         try {
            const data3 = await fetchThree(data2)
          // handle data3
         } catch (e) {
          // handle error
         }
      } catch (e) {
       // handle error
      }
   } catch (e) {
    // handle error
   }
}

Это ад гнездования Async Await, я люблю называть его Scope Hell.

И, конечно же, если у Promise есть горизонтальный ад (Bridge Hell), то у Async Await тоже есть вертикальный ад, представляя Tower Hell:

Башня Ада

const abc = async () => {
   let data = null
  
   try {
      data = await fetchOne()
   } catch (e) {
      // handle error
      return
   }
  
   let data2 = null
  
   try {
      data2 = await fetchTwo(data)
   } catch (e) {
      // handle error
      return
   }
  
   try {
      const data3 = await fetchThree(data2)
      // handle data3
   } catch (e) {
      // handle error
   }
}

Это вертикальный ад Async Await, также известный как Tower Hell.

Вы можете подумать, что на самом деле это нормально, он не вложенный, именно так мы обычно пишем наш код, сверху вниз, так что же делает его адом?

Причина в том, что он создает изменяемую переменную для каждого промиса.

Это потому, что блок try catch не является выражением. Его нельзя преобразовать в значение. Это не декларативно.

По той же причине, по которой функциональный язык использует рекурсию вместо цикла for, потому что цикл for не является выражением и должен полагаться либо на изменчивость, либо на побочный эффект, чтобы быть полезным.

Так что же не так с изменяемыми переменными?

Их трудно отслеживать и отлаживать, требуя дополнительных усилий, чтобы определить, когда и где мы их мутируем.

## Подведение итогов 4 Ада

Promise’s Callback Hell
❌ трудно объяснить из-за вложенности

Promise’s Bridge Hell
✅ может читать код слева направо
❌ сложно рассуждать из-за длинной цепочки методов

Async Await’s Scope Hell
❌ сложно рассуждать из-за вложенности

Async Await’s Tower Hell
✅ может читать код сверху вниз
❌ сложно рассуждать из-за изменяемых переменных

Превращение ада в рай

Итак, можем ли мы создать что-то, что будет:
✅ не вложенным
✅ без длинной цепочки
✅ без изменяемой переменной
✅ читать код сверху вниз
✅ легко использовать

По привычке мы предпочитаем читать код сверху вниз, а не слева направо.

Это возможно путем исправления существующих адов.

Стратегия:
1. Выберите ады, которые нужно исправить
2. Разработайте стратегию, чтобы исправить ады

Какие ады оставить?

Ответ заключается в том, чтобы сохранить Bridge Hell от Promise и Tower Hell от Async Await.

Это связано с тем, что вложенность является общей проблемой стиля кода, которая не ограничивается Promise и Async Await. Поэтому нам нужно полностью отказаться от Callback Hell в Promise и Scope Hell в Async Await.

Как их исправить?

Давайте поближе познакомимся с Bridge Hell от Promise и Scope Hell от Async Await.

Promise’s Bridge Hell
✅ не страдает от изменяемых переменных
❌ Страдает от длинной цепочки методов

Async Await’s Tower Hell
✅ не страдает от длинных цепочек методов
❌ Страдает от изменяемых переменных

На этом этапе становится ясно, что, поскольку мы можем смешивать Promise и Async Await, мы можем:

1. **Используйте Promise для создания неизменяемых переменных**
2. **Используйте Async Await для разделения длинной цепочки.**

Небесное решение!

const abc = async () => {
   const { data, error } = await fetchOne()
    .then(data => ({ data }))
    .catch(error => ({ error }))
  
   if (error) {
    // handle error
    return
   }
  
   const { data2, error2 } = await fetchTwo(data)
    .then(data2 => ({ data2 }))
    .catch(error2 => ({ error2 }))
  
   if (error2) {
    // handle error
    return
   }
  
   const { data3, error3 } = await fetchThree(data2)
    .then(data3 => ({ data3 }))
    .catch(error3 => ({ error3 }))
  
   if (error3) {
    // handle error
    return
   }
   // handle data3
}

Что нам нужно сделать, так это `await` Promise и вернуть объект `{data}` из методов `.then` и объект `{error }` из методов `.catch`.

Давайте рассмотрим преимущества:

✅ нет вложенности
✅ нет длинной цепочки
✅ нет изменяемых переменных
✅ читается код сверху вниз
✅ прост в использовании

Последние мысли

Поскольку Promise и Async Await делают одно и то же и концептуально похожи, но структурно различаются с точки зрения сильных и слабых сторон, их отношения на самом деле скорее симбиоз, чем конкуренция.

Я надеюсь, что эта статья вдохновит вас на написание лучшего кода Promise и Async Await.

Оставайтесь творческими и инновационными коллегами-программистами!