Ожидание цепочки обещаний в JavaScript

Часто говорят, что async/await и then() нельзя смешивать.

Однако рассмотрим сценарий, в котором нам нужна служебная функция, которая извлекает пользовательские данные, которые мы хотим await использовать при использовании в другом месте базы кода:

const getUser = async () => {
  const userPromise = fetch(...)
    .then((res) => res.json())
    .then((user) => JSON.stringify(user));

  return userPromise;
};

Если я не ошибаюсь, приведенное выше позволяет нам преобразовать возвращаемое значение промиса fetch в промис, который возвращает окончательные пользовательские данные в формате json, когда await будет позже:

const user = await getUser();

Моя идея заключалась в том, что вложение then() здесь гарантирует, что нам не нужно писать строки для распаковки json при использовании getUser позже.

Это правильный вариант использования для смешивания then и async/await, или это антипаттерн?

Единственная проблема при смешивании async/await и promise API — это когда они буквально смешиваются в одном месте. Нет, если они используются в одном проекте. Более того, «проблема» их смешивания не является технической. Это просто может привести к путанице с двумя разными семантиками. Хотя на самом деле это одно и то же, в конце концов. Такая путаница может привести к таким вещам, как Является ли анти-шаблоном использование async/await внутри нового конструктора Promise()? но смесь сама по себе не плохая.

VLAZ 21.02.2023 21:11

Короче говоря, можно смешивать обещания API и async/await, если вы делаете это правильно. Не тогда, когда это приводит к странным и потенциально проблематичным взаимодействиям. Если вы не знаете, когда это так, придерживайтесь только одного для каждой функции. Но у каждой функции может быть свой подход.

VLAZ 21.02.2023 21:12

@ВЛАЗ Спасибо. Верен ли мой пример кода, или я неправильно понял, как это работает? Как вы, наверное, поняли, я не хочу ждать внутри служебной функции, так как использую служебную функцию при параллельной выборке данных и т.д.

Magnus 21.02.2023 22:10

Это синтаксически допустимо. Я также предполагаю, что вы хотите. Но .then((res) => res.json()).then((user) => JSON.stringify(user)); не имеет особого смысла. Разберите JSON в объект JS, затем преобразуйте объект JS обратно в JSON. Если вам нужен JSON, проще просто использовать .then(res => res.text()). Хотя код также не обрабатывает сбои с fetch() или даже получение ответа об ошибке 4xx или 5xx. Итак, это правильно? ¯\_(ツ)_/¯ Вероятно, нет. Но вы также, вероятно, используете его в иллюстративных целях. В этом случае «правильно» вряд ли применимо в любом случае.

VLAZ 21.02.2023 22:14

@VLAZ Позвольте мне проверить .text(). Мне нужен был пользователь в компоненте React, поэтому stringify.

Magnus 21.02.2023 22:23

В комментарии к удаленному ответу вы, кажется, думаете, что await блокирует, но это не так. Вы также можете использовать await здесь. Функция, которая не использует await, не должна быть async.

trincot 21.02.2023 23:04

@trincot Спасибо за комментарий. Await по определению ожидает возврата выборки. Я не хочу ждать этого сразу. Вы можете начать выборку данных выше в дереве компонентов вместе с кучей других асинхронных выборок, а затем ожидать ее ниже. С компонентами React Server и потоковой передачей / приостановкой вы избежите блокировки пользовательского интерфейса. См., например, здесь: beta.nextjs.org/docs/data-fetching/…

Magnus 22.02.2023 06:11

@trincot PS: я уверен, что вы знаете, но fetch — это веб-API, который работает вне JS Engine в отдельных потоках. Это не синхронно. Часто бывает разумно начать извлечение данных на ранней стадии, когда вы считаете, что они могут вам понадобиться.

Magnus 22.02.2023 06:14

«Await по определению ожидает возврата выборки» await — это синтаксический сахар над обещанием API. Неважно, делаете ли вы x = await p; console.info(x) или p.then(x => console.info(x). В любом случае это будет та же самая операция. Функция с ожиданиями не быстрее и не медленнее, и на самом деле она не работает иначе, чем функция только с .then() вызовами.

VLAZ 22.02.2023 07:16

@VLAZ Спасибо, тогда я думаю, что неправильно понял then(). Оба await и then() останавливают выполнение JS до тех пор, пока выборка не будет выполнена (т.е. пока обещание не будет разрешено)?

Magnus 22.02.2023 07:28

Ни один из них не останавливает выполнение. await p приостановит функцию и перейдет к выполнению, чтобы другие вещи могли работать. Когда p устанавливается, функция возобновляется и снова ставится в очередь для продолжения работы. По сути, преобразование x = await p; console.info(x) в p.then(x => console.info(x)

VLAZ 22.02.2023 07:32

@VLAZ Хммм, не уверен, что согласен (хотя могу ошибаться). Смотрите это: function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; const foo = await timeout(3000); console.info(foo); Последний оператор не будет выполняться до тех пор, пока не истечет время ожидания и обещание не будет разрешено.

Magnus 22.02.2023 08:03

Пример @Bergi здесь: stackoverflow.com/questions/33289726/…

Magnus 22.02.2023 08:05

@Magnus не запускается, но и не блокируется. Казнь не остановлена.

VLAZ 22.02.2023 08:05

@VLAZ Хорошо, думаю, я понял твою точку зрения. Позвольте мне перефразировать. Ничто ниже ожидания в текущем контексте выполнения не будет выполняться до тех пор, пока обещание не вернется. Это верно?

Magnus 22.02.2023 08:06

Функция не будет продолжена. Но это то же самое, если бы у вас был .then() — обратный вызов срабатывает только после того, как обещание разрешено. С await более поздний код также работает после разрешения промиса.

VLAZ 22.02.2023 08:07

В примере кода, которым вы поделились, это не изменило бы поведение возвращаемого промиса, если бы вы использовали await. Я думаю, что у вас есть недопонимание по поводу await. Конечно, есть различия с then, но приведенный вами пример кода не демонстрирует такой разницы. Использование здесь await не помешает параллельной обработке других выборок. Ваши комментарии к уже удаленным ответам показывают непонимание await. Нет блокировки пользовательского интерфейса.

trincot 22.02.2023 08:48

Спасибо вам обоим, я вернусь к чертежной доске и изучу ее дальше.

Magnus 22.02.2023 09:11
Руководство для начинающих по веб-разработке на React.js
Руководство для начинающих по веб-разработке на React.js
Веб-разработка - это захватывающая и постоянно меняющаяся область, которая постоянно развивается благодаря новым технологиям и тенденциям. Одним из...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
Интервьюер: Почему '[] instanceof Object' возвращает "true"?
Интервьюер: Почему '[] instanceof Object' возвращает "true"?
Все мы знаем, что [] instanceof Array возвращает true, но почему [] instanceof Object тоже возвращает true?
Абстрактное синтаксическое дерево (AST) и как оно работает с ReactJS
Абстрактное синтаксическое дерево (AST) и как оно работает с ReactJS
Абстрактное синтаксическое дерево (AST) - это древовидная структура данных, которая представляет структуру и иерархию исходного кода на языке...
2
19
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать блок try/catch внутри функции async. Попробуйте запустить это:

  const getUser = async () => {
    try {
      const response = await fetch(...)
      const user = await res.json()
      return JSON.stringify(user);
    } catch (err) {
      throw Error(err);
    }
};

Это противоречит цели. Я не хочу ждать пользователя внутри служебной функции из-за параллельной выборки данных и т. д. –

Magnus 21.02.2023 22:12
Ответ принят как подходящий

Async..await — это чисто синтаксический сахар. Каждый .then может быть представлен в async..await и наоборот. Однако смешивание этих двух вещей, как правило, является запахом кода, который может сигнализировать о том, что автор также не полностью понимает. Например, в вашей функции используется ключевое слово async, но нет выражения await, что делает ее совершенно ненужной.

const getUser = async () => { // ❌ no need for async
  const userPromise = fetch(...)
    .then((res) => res.json())
    .then((user) => JSON.stringify(user)); // ❌ re-encode JSON, why?

  return userPromise;
};

Если вы хотите вернуть пользовательский JSON, рассмотрите response.text -

const getUser = () =>
  fetch(...).then(res => res.text()) // ✅

Кроме того, комментарии указывают на то, что вы считаете, что async..await предотвращает «параллельные» асинхронные операции. Это просто не так. Каждое выражение then имеет совершенно эквивалентное представление async..await -

const getUser = async () => {
  const res = await fetch(...)
  return res.text() // ✅ await is not needed here!
}

Примечание return await .. — это анти-шаблон, поскольку async функции неявно возвращают обещания!

Другие вопросы по теме