DanLevy.net

async/awaitを無理に使おうとするのはやめよう

Promiseは今まさに旬な存在

Hero image for async/awaitを無理に使おうとするのはやめよう

有史以来、開発者は多くのばかげた論争を繰り広げてきた。古典的な「タブ対スペース」から、永遠の「Mac対PC」論争まで、私たちは気を逸らす議論を見つけるのが得意だ。


答え: Linuxとスペース。

論争…?

Promise対Async/Await!

待てよ、これは論争なのか?そうに違いないよね?もうコールバックについて話さなくなったようだ?

いや、これは論争ではない。究極的には、あなたのツールボックスにもう一つ追加される潜在的なツールだ。しかし、async/awaitはすべてのPromise機能を置き換えるものではない(特にPromise.all.race)ため、それを置き換えとして提示するのは誤解を招く。

async/awaitがPromiseの置き換えであるとみんなが待ち望んでいた宣伝している影響力のある人々がたくさんいる。

ヒント:いいえ、とんでもない、全くそんなことはない。

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

Promiseが嫌いで、このリファクタリング機能が欲しいとしても、責めるつもりはない。


共感する。わかるよ。


私もそうだった。🤗


私も以前はPromiseが嫌いだった。今日では、完全に考えが変わった。Promiseは素晴らしい。 Promiseを使うことで、関数合成を活用することができるようになるのだ。

Promiseの技術を向上させるために、まず集中して取り組むべき2つの領域をお勧めする。

  1. 名前付き関数(無名関数なし)
  2. 単一目的の関数

#1: 名前付き関数!

無名関数を捨てよう。名前付き関数を使うことで、コードが要件の詩のように読みやすくなる。

よくある例を見てみよう:

fetchを使ってHTTP GETリクエストを送信する:

アンチパターン

// ❌ 無名のインライン関数を使う 💩
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()
}

このアプローチの利点は、DRYなコードになればなるほど明らかになってくる。

追加リソース: このテクニックを使った基本的なロギング高度なデバッグ1分動画をチェックしてほしい。

#2: 単一目的(関数)

一見すると正確に聞こえる:単一目的。

しかしそれは非常に主観的で恣意的であり、確かに時には無意味でさえある。

興味深いことに、ほとんどの開発者は自分が単一目的のコードを書くのが_かなり上手だ_と報告している。無関係ではないが、彼らは自分たちが優れたドライバーでもあると報告している!

(非常に才能ある)Jake ArchibaldがGoogle Developersサイト向けのasync/await記事で紹介している例を見てみよう(注: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. インラインのHTTP GETに適用する:
  3. HTTP fetch
  4. レスポンスのテキストボディを返す
  5. textPromiseの各promiseの後に.then(text => console.log(text))を追加する
  6. 結果を逐次的に出力する

この単一の関数の中に5つの無名メソッドが定義されている。Jake自身が指摘しているように、.reduceは複雑すぎる。このような微妙なメカニズムをコードの至る所に手書きするのは理にかなっていない。言い換えると、私たちはdocument.createElement()element.setAttribute()などを延々と書いてDOMを作成したりしない。代わりに、多くの選択肢の中から最適なツールを選ぶ:ヘルパー/ユーティリティ関数、ライブラリ、またはフレームワークだ。

解決策:単一目的の関数

メソッドの抽出から始めよう…

VS Code refactor extracting async methods from Promise code

.reduce()logPromise()Promise.all..map()に置き換えて続けよう…

Refactored Promise chain using Promise all and map for readability

まとめ

これらのテクニックを自分のコードに試してみよう!その後、ツイートで教えてほしい。うまくいったかどうか、あるいは質問やコメントがあれば、ぜひ連絡を!

#PromiseTruthを広め、この記事を共有しよう。❤️

credit: matt-nelson-414464-unsplash.webp

関連記事