Интенсивное чтение «async/await — палка о двух концах»

внешний интерфейс React.js jQuery Redux

Интенсивное чтение на этой неделе«Побег из асинхронного/ожидающего ада».

1. Введение

Наконец, жаловались на async/await. Адитья Агарвал считает, что синтаксис async/await доставляет нам новые неприятности.

На самом деле автор давно чувствовал, что что-то не так, и наконец кто-то сказал правду, async/await может вызвать проблемы.

2 Обзор

Вот современный интерфейсный код везде:

(async () => {
  const pizzaData = await getPizzaData(); // async call
  const drinkData = await getDrinkData(); // async call
  const chosenPizza = choosePizza(); // sync call
  const chosenDrink = chooseDrink(); // sync call
  await addPizzaToCart(chosenPizza); // async call
  await addDrinkToCart(chosenDrink); // async call
  orderItems(); // async call
})();

С самим синтаксисом await проблем нет, но иногда пользователь может использовать его неправильно. когдаpizzaDataа такжеdrinkDataКогда между ними нет зависимостей, последовательное ожидание максимально удвоит время выполнения.getPizzaDataвремя работы, потому чтоgetPizzaDataа такжеgetDrinkDataдолжны выполняться параллельно.

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

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

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

(async () => {
  const pizzaPromise = selectPizza();
  const drinkPromise = selectDrink();
  await pizzaPromise;
  await drinkPromise;
  orderItems(); // async call
})();

или использоватьPromise.allДелает код более читабельным:

(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems); // async call
})();

Кажется, что не нужно ждать произвольно, это может снизить производительность вашего кода.

3 Интенсивное чтение

Тщательно подумав о том, почему злоупотребляют async/await, я думаю, что его функция относительно нелогична.

Во-первых, async/await — это действительно синтаксический сахар, и его функция заключается только в том, чтобы сделать написание кода более удобным. Не глядя на его синтаксис или особенности, просто по трем словам синтаксического сахара видно, что он должен ограничивать некоторые возможности.

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

Функциональная целостность и простота использования всегда конкурировали друг с другом.Различные версии многих фреймворков с открытым исходным кодом — это почти результат смешения функциональной целостности и удобства в разных пропорциях.

Итак, вернемся к async/await. Проблема, которую он решает, — это катастрофа ада обратных вызовов:

a(() => {
  b(() => {
    c();
  });
});

Чтобы уменьшить влияние слишком большого количества вложенных структур на мозг, async/await решил написать:

await a();
await b();
await c();

Хотя уровень согласован, он все же логически вложен.Разве это не еще одна степень увеличения нагрузки на мозг? И это преобразование невидимо, поэтому мы часто игнорируем его, что приводит к злоупотреблению синтаксическим сахаром.

понимать синтаксический сахар

Хотя правильно понимать реальный эффект от async/await противочеловечно, чтобы очистить структуру кода и не допустить написания низкопроизводительного кода, необходимо серьезно понимать изменения, привносимые async/await.

Прежде всего, async/await может реализовать только часть функций, поддерживаемых обратными вызовами, то есть удобно иметь дело только с вложенными сценариями. В других сценариях вам нужно использовать некоторые мозги.

Например, две пары обратных вызовов:

a(() => {
  b();
});

c(() => {
  d();
});

Если она написана следующим образом, хотя функция гарантированно непротиворечива, она становится наименее эффективным методом выполнения:

await a();
await b();
await c();
await d();

Поскольку он переводится в обратный вызов, он становится:

a(() => {
  b(() => {
    c(() => {
      d();
    });
  });
});

Однако мы обнаружили, что в исходном коде функцияcС участиемaвыполняться одновременно, но синтаксис async/await заставляет насbПосле выполнения выполнитьc.

Поэтому, когда мы это осознаем, мы можем немного оптимизировать производительность:

const resA = a();
const resC = c();

await resA;
b();
await resC;
d();

Но на самом деле эта логика не может добиться эффекта обратного вызова, хотяaа такжеcвыполняются одновременно, ноdИзначально просто ждалcзакончено, теперь, еслиaсоотношение времени выполненияcдолго становится:

a(() => {
  d();
});

Кажется, что он полностью изолирован только от двух функций:

(async () => {
  await a();
  b();
})();

(async () => {
  await c();
  d();
})();

или использоватьPromise.all:

async function ab() {
  await a();
  b();
}

async function cd() {
  await c();
  d();
}

Promise.all([ab(), cd()]);

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

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

Первоначальный автор далPromise.allМетод упрощает логику, но автор считает, что можно повысить читабельность кода, не игнорируя синтаксис async/await и при необходимости соответствующим образом используя обратные вызовы.

4 Резюме

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

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

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

Точно так же, как async/await hell, видя такой редукс-код, я думаю, что он намного уступает коду jquery, написанному так называемым старым интерфейсом, который не идет в ногу со временем.

Качество кода определяется мышлением, а не фреймворком или синтаксисом.Async/await хорош, но в меру.

еще 5 обсуждений

Адрес обсуждения:Интенсивное чтение «Побег из ада async/await» · Выпуск №82 · dt-fe/weekly

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