Часто говорят, что 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, или это антипаттерн?
Короче говоря, можно смешивать обещания API и async/await, если вы делаете это правильно. Не тогда, когда это приводит к странным и потенциально проблематичным взаимодействиям. Если вы не знаете, когда это так, придерживайтесь только одного для каждой функции. Но у каждой функции может быть свой подход.
@ВЛАЗ Спасибо. Верен ли мой пример кода, или я неправильно понял, как это работает? Как вы, наверное, поняли, я не хочу ждать внутри служебной функции, так как использую служебную функцию при параллельной выборке данных и т.д.
Это синтаксически допустимо. Я также предполагаю, что вы хотите. Но .then((res) => res.json()).then((user) => JSON.stringify(user)); не имеет особого смысла. Разберите JSON в объект JS, затем преобразуйте объект JS обратно в JSON. Если вам нужен JSON, проще просто использовать .then(res => res.text()). Хотя код также не обрабатывает сбои с fetch() или даже получение ответа об ошибке 4xx или 5xx. Итак, это правильно? ¯\_(ツ)_/¯ Вероятно, нет. Но вы также, вероятно, используете его в иллюстративных целях. В этом случае «правильно» вряд ли применимо в любом случае.
@VLAZ Позвольте мне проверить .text(). Мне нужен был пользователь в компоненте React, поэтому stringify.
В комментарии к удаленному ответу вы, кажется, думаете, что await блокирует, но это не так. Вы также можете использовать await здесь. Функция, которая не использует await, не должна быть async.
@trincot Спасибо за комментарий. Await по определению ожидает возврата выборки. Я не хочу ждать этого сразу. Вы можете начать выборку данных выше в дереве компонентов вместе с кучей других асинхронных выборок, а затем ожидать ее ниже. С компонентами React Server и потоковой передачей / приостановкой вы избежите блокировки пользовательского интерфейса. См., например, здесь: beta.nextjs.org/docs/data-fetching/…
@trincot PS: я уверен, что вы знаете, но fetch — это веб-API, который работает вне JS Engine в отдельных потоках. Это не синхронно. Часто бывает разумно начать извлечение данных на ранней стадии, когда вы считаете, что они могут вам понадобиться.
«Await по определению ожидает возврата выборки» await — это синтаксический сахар над обещанием API. Неважно, делаете ли вы x = await p; console.info(x) или p.then(x => console.info(x). В любом случае это будет та же самая операция. Функция с ожиданиями не быстрее и не медленнее, и на самом деле она не работает иначе, чем функция только с .then() вызовами.
@VLAZ Спасибо, тогда я думаю, что неправильно понял then(). Оба await и then() останавливают выполнение JS до тех пор, пока выборка не будет выполнена (т.е. пока обещание не будет разрешено)?
Ни один из них не останавливает выполнение. await p приостановит функцию и перейдет к выполнению, чтобы другие вещи могли работать. Когда p устанавливается, функция возобновляется и снова ставится в очередь для продолжения работы. По сути, преобразование x = await p; console.info(x) в p.then(x => console.info(x)
@VLAZ Хммм, не уверен, что согласен (хотя могу ошибаться). Смотрите это: function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; const foo = await timeout(3000); console.info(foo); Последний оператор не будет выполняться до тех пор, пока не истечет время ожидания и обещание не будет разрешено.
Пример @Bergi здесь: stackoverflow.com/questions/33289726/…
@Magnus не запускается, но и не блокируется. Казнь не остановлена.
@VLAZ Хорошо, думаю, я понял твою точку зрения. Позвольте мне перефразировать. Ничто ниже ожидания в текущем контексте выполнения не будет выполняться до тех пор, пока обещание не вернется. Это верно?
Функция не будет продолжена. Но это то же самое, если бы у вас был .then() — обратный вызов срабатывает только после того, как обещание разрешено. С await более поздний код также работает после разрешения промиса.
В примере кода, которым вы поделились, это не изменило бы поведение возвращаемого промиса, если бы вы использовали await. Я думаю, что у вас есть недопонимание по поводу await. Конечно, есть различия с then, но приведенный вами пример кода не демонстрирует такой разницы. Использование здесь await не помешает параллельной обработке других выборок. Ваши комментарии к уже удаленным ответам показывают непонимание await. Нет блокировки пользовательского интерфейса.
Спасибо вам обоим, я вернусь к чертежной доске и изучу ее дальше.
Вы можете использовать блок 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);
}
};
Это противоречит цели. Я не хочу ждать пользователя внутри служебной функции из-за параллельной выборки данных и т. д. –
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 функции неявно возвращают обещания!
Единственная проблема при смешивании async/await и promise API — это когда они буквально смешиваются в одном месте. Нет, если они используются в одном проекте. Более того, «проблема» их смешивания не является технической. Это просто может привести к путанице с двумя разными семантиками. Хотя на самом деле это одно и то же, в конце концов. Такая путаница может привести к таким вещам, как Является ли анти-шаблоном использование async/await внутри нового конструктора Promise()? но смесь сама по себе не плохая.