DanLevy.net

Перестаньте пытаться сделать async/await главным

Промисы сейчас в тренде

Hero image for Перестаньте пытаться сделать async/await главным

С незапамятных времён разработчики ведут бессмысленные споры. От классического «Табы против пробелов» до вечного «Mac против PC» — мы умеем находить отвлекающие аргументы.


Ответы: Linux и пробелы.

Битва…?

Промисы против Async/Await!

Подождите, это вообще битва? Должна же быть, правда? Мы больше не говорим о колбэках?

Нет, это не битва. В конечном счёте это ещё один потенциальный инструмент в вашем арсенале. Однако, поскольку async/await не заменяет всю функциональность промисов (в частности, Promise.all, .race) ошибочно представлять его как замену.

Многие влиятельные люди продвигают это заблуждение, будто async/await — это замена промисам, которую все так долго ждали.

Подсказка: нет, нет и ещё раз нет.

Недавнее дополнение к VS Code усиливает этот перекос. Как написал @umaar:

Visual Studio Code can now convert your long chains of Promise.then()‘s into async/await! 🎊 Works very well in both JavaScript and TypeScript files. .catch() is also correctly converted to try/catch ✅ pic.x.com/xb39Lsp84V

— Umar Hansa (@umaar) September 28, 2018

Если вы ненавидите промисы и хотите такую функцию рефакторинга, я не виню вас.


Я сопереживаю. Я понимаю.


Я был там. 🤗


Раньше я ненавидел промисы. Сегодня я полностью изменил своё мнение. Промисы великолепны. Они позволяют и побуждают вас использовать преимущества функциональной композиции.

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

  1. Именованные функции (никаких анонимных)
  2. Функции с единственной ответственностью

#1: Именованные функции!

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

Рассмотрим распространённый пример:

Выполнение HTTP GET-запроса с помощью fetch:

Антипаттерн

// ❌ Использование анонимных inline-функций 💩
fetch(url)
.then(response => response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus)))
.then(response => response.text())

Решение: Именованные методы

// ✅ Поясняется ясность: именованные функции
fetch(url)
.then(checkResponse)
.then(getText)
// Переиспользуемые функции общего назначения
function checkResponse(response) {
return response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus))
}
function getText(response) {
return response.text()
}

Преимущества этого подхода становятся всё очевиднее по мере того, как ваш код становится суше.

Дополнительные ресурсы: Посмотрите мои 1-минутные видео о базовом логировании и продвинутой отладке с использованием этой техники.

#2: Единственная ответственность (функций)

Это звучит обманчиво точно: единственная ответственность.

И всё же это настолько субъективно и произвольно, что иногда даже бессмысленно.

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

Рассмотрим пример, который (невероятно талантливый) Jake Archibald приводит в своей статье об async/await для сайта Google Developers (примечание: 2024, ссылка удалена).

// source: https://developers.google.com/web/fundamentals/primers/async-functions
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});
// log them in order
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}

Единственная ответственность?

Я бы сказал, нет. Что делает logInOrder?

  1. Проходит циклом по списку urls
  2. Применяет к ним inline HTTP GET:
    1. HTTP fetch
    2. Возвращает текстовое тело ответа
  3. Добавляет .then(text => console.log(text)) после каждого промиса в textPromise
    1. Выводит результаты последовательно

В этой единственной функции определено 5 анонимных методов. Как указывает даже Джейк, .reduce здесь слишком сложен. Не имеет смысла вручную писать такие механизмы повсюду в коде. Другими словами, мы не создаём DOM с помощью бесконечных document.createElement(), element.setAttribute() и т.д. Вместо этого мы выбираем лучший инструмент из множества вариантов: вспомогательные/утилитарные функции, библиотеки или фреймворки.

Решение: Функции с единственной ответственностью

Начните с извлечения методов

Рефакторинг VS Code: извлечение асинхронных методов из кода с промисами

Продолжите, заменив .reduce() и logPromise() на Promise.all и ..map()

Рефакторенная цепочка промисов с использованием Promise.all и map для читаемости

Итог

Попробуйте применить эти техники к своему коду! Затем напишите мне в Twitter и расскажите, как прошло. Или если у вас есть вопросы или комментарии, свяжитесь со мной!

Помогите распространить #PromiseTruth и поделитесь этой статьёй. ❤️

credit: matt-nelson-414464-unsplash.webp

Дополнительное чтение