JavaScript の Async/Await が Promise より優れている7つの理由【翻訳】
本記事は英記事を勝手に翻訳したものです。
Mostafa Gaafar さんの元記事 dev.to
概要
async/await は NodeJS7.6 で導入され、現在も全てのモダンブラウザでサポートされています。私はJSにおいて2017年から1番の変更だと思っています。もしそう思われない方がいたら、なぜ採用すべきかをサンプルとともに理由を以下に述べていきたいと思います。
Async/Await 101
Async/Await を聞いたことがない方々のために、簡単に概要を。
async/await は非同期なコードを書く新しい方法です。従来の非同期プログラミングの手法はコールバックかPromiseです。
async/await は単なる Promise のシンタックスシュガーです。コールバックで利用することはできません。
async/await は promise のようなもので、ノンブロッキングです。
async/await は同期的な見た目で非同期なコードを作ることができます。これが一番の特徴です。
シンタックス
ここで getJSON
という promise を返す関数を定義し、promise はいくつかの JSON データを resolve します。これをログに出力し、 "done"
を返します。
そしてこちらが async/await を使ったコードです。
ここには3つの違いがあります。
1. この関数の前には async
キーワードが記述されており、 await
キーワードは async
関数内でのみ使うことができます。どんな async
関数も暗黙的に promise を返し、promise で resolve される値が return
で返されます。(このケースでは "done"
が文字列で返されます。)
2. await
は async
関数内ではないトップレベルで使うことができません。
3. await getJSON()
は console.log が getJSON()
のpromiseのresolveを待ってから呼び出すことを暗黙的に意味しています。
なぜこれ(async/await)が良いのか?
1. 簡潔できれい
コードは書くことより読むことの方が多いです。上記のような恣意的な例だとしてもも、読みやすいコードになることは分かってもらえると思います。.then
を書かずに非同期関数でレスポンスをハンドリングできます。上記の例では、無駄に data
という変数を定義する必要もなくなります。ネストするコードを避けることもできます。小さな利点ですが、そのメリットは明らかだと思います。
2. エラーハンドリング
async/await は昔ながらの try/catch
を使うので、同期、非同期で発生する両方のエラーをハンドリングすることができます。例えば promise では JSON.parse
で fail しても promise 内で起きたエラーなので、 try/catch
しないとエラーハンドリングできません。promise のエラーハンドリングを行うために多重に catch を定義する必要があるのです。
async/await で同じコードを書くと、catch
でどちらのエラーもハンドリング可能です。
3. 条件式
あるデータを fetch して取得したデータの内容によっては、より詳細なデータを取得するコードを想像してみてください。
頭痛がしてきそうなコードですね。6段のネストが発生していますし、返り値のデータの伝搬が必要になります。
以下が async/await でより読みやすく書き直した例になります。
4. 即値
promise1
を呼び、その返り値を使って promise2
を呼び、それらの返り値を使って promise3
を呼ぶような状況があったとします。おおよそ以下のようなコードになるでしょう。
もし promise3
で value1
が必要じゃないときは、もう少し簡単に書くことができます。Promise.all
を使って value1 と value2 を包めばネストを避けることはできます。
このアプローチは可読性のためにセマンティクスを犠牲にしています。ネストを避けるためだからといって value1 と value2 が配列である必要性はないはずです。 この同じロジックを async/await を使って簡易的で直感的なものに書き直してみます。
5. エラーのスタック
複数のpromiseをチェーンして呼び出すコードを想像してください。このチェーン上のどこかでエラーがスローされます。
このエラースタックはpromiseチェーンで返されますが、どこで発生したのか何の手がかりもありません。さらにこれはミスリーディングで、callAPromise
という名前で定義されているとファイルや行数が出力されたとしても、そのエラーがどの関数で発生したのかわかりません。
async/await なら、どの関数でエラーが発生するかわかります。
これはローカル環境で開発しているときの大きいメリットというわけではありませんが、サーバ上で発生したエラーログから挙動を把握する際に役に立つことでしょう。
6. デバッグ
async/await はより簡単にデバッグ可能です。promise のデバッグでは以下2つの苦しみがあります。
6-1. アロー関数内でブレークポイントをセットできない。
6-2. ブレークポイントを .then
内でセットしたらステップオーバーでデバッグすることはできます。ただし同期的に "ステップ" するので、 debugger は .then
内では動かすことができません。
async/await はアロー関数にする必要がないので、await で同期的に書いているコードを正確に "ステップ" することができます。
7. 値でも await を使える
最後に、await は同期でも非同期でもどちらの式としても使うことができます。例えば、 Promise.resolve(5)
は await 5
と書くことができます。これはとても有用なことで、入力が同期か非同期か不明な関数を使うとしても、それに関係なく await を使って書くことができます。
アプリケーション内で、あるAPI呼び出しによって時刻を記録したいとします。このために関数を定義する際に以下のように書いたとします。
全てのAPI呼び出しはpromiseを返しますが、同じ関数を同期関数内で時刻を記録するために使いたくなったらどうしますか?同期関数内でpromiseを返さなかったらエラーをスローしてしまいます。そのため makeRequest()
は Promise.resolve()
で包む必要があります。
async/await を使えば、 await
は promiseでも値でも安全に動作するので、心配する必要はありません。
まとめ
async/await はここ数年でJavaScriptに追加された最大の革新的機能の一つです。これはpromiseのようなややこしいシンタックスを直感的なものに置き換えるものです。
懸念事項
async/await はシンタックスからは動作が明白なものではないため、少しの懸念があります。コールバックや .then
を見た時の挙動は非同期なものとして理解しやすいですが、新しい記述の仕方(async/await)には慣れるまでに時間がかかることでしょう。C# ではasync/awaitを何年も前から用意されているので、より一層これに慣れ親しんでいる人たちはわかりづらく不便だと感じるかもしれません。
後記
async/await と promise を比較してメリットを述べている記事はあまり日本語で見かけなかったので、3、4日前にあがった記事を日本語訳した。 async/await はおおよそメジャーな書き方になってきたものの、 javascript 初心者からするとあまり理解しづらい部分もあると思うので、 promise との違いを分かってもらえれば嬉しい。 元記事はあくまで一所感であると思うし、ちょっと偏った意見だなと思うところもあるので注意してもらいたい。 また、翻訳が間違っているなど問題があれば指摘してほしい。