mongolyyのブログ

開発(Javascript, Typescript, React, Next.js)や開発手法(スクラム, アジャイル)、勉強したことについて色々書ければと。

async/await, thenの使い分け

はじめに

会社の同期から、Javascriptの非同期処理について、「async/awaitとthenでどっちを使ったほうがいいのだろうか?」と聞かれて、基本はasync/awaitでよいけど、thenの方が良い場合もあるなーと思ったので、調査、整理してみました。

async/awaitが優れている点について

async/awaitはES2016で追加された文法なので、それまでのthenが抱えていた課題を改善、解決する、非同期処理の記述が可能になります。

具体的には以下のようなメリットが有ると思っています(個人的意見)

  • 複数の非同期処理について、依存関係が発生してくる場合に書きやすい
  • エラーハンドリングが見やすい
  • Javascriptエンジンによっては早くなる
  • thenを使ったときと比べてネストが発生しない

一つ一つについてコメントしたいと思います。

非同期処理について、複雑に依存関係がある場合に書きやすい

async/awaitを使用すると、非同期処理に依存関係は発生している場合効果を発揮してくると思っています。
以下のように、同期処理を記載するとほとんど同じ書き味で書くことができます。

const asyncFunc = async () => {
    const getUserId = () => Promise.resolve(...) // APIを叩いてuserIdを取得する関数
    const getGroup = (userId) => Promise.resolve(...) // APIを叩いて、Promiseでラップされた引数で与えられたユーザーが所属するgroup情報を取得する関数
    const getDepartment = (userId) => Promise.resolve(...) // APIを叩いて、Promiseでラップされた引数で与えられたユーザーが所属するdepartment情報を取得する関数

    const userId = await getUserId()
    const groupId = await getGroup(userId)
    console.log(groupId)
    const departmentId = await getDepartment(userId)
    console.log(departmentId)
}

一方、これをthenで書こうとすると、次のように書くことができます。
Promise.allを使用したり、ネストが発生したり、async/awaitと比べると見づらいコードになっているかなーと感じます。

const asyncFunc = () => {
    const getUserId = () => Promise.resolve(...) // APIを叩いてuserIdを取得する関数
    const getGroup = (userId) => Promise.resolve(...) // APIを叩いて、Promiseでラップされた引数で与えられたユーザーが所属するgroup情報を取得する関数
    const getDepartment = (userId) => Promise.resolve(...) // APIを叩いて、Promiseでラップされた引数で与えられたユーザーが所属するdepartment情報を取得する関数

    getUserId().then(userId => {
        Promise.all([getGroup(userId), getDepartment(userId)]).then((values) => {
                values.map(value => console.log(value))
        }
    })
}

エラーハンドリングが見やすい

JavaC++と同様のtry/catchが使えるので、エラーハンドリングが見やすいです。
一方、then/catchはメソッドチェーンになるので、catchを書き忘れていても気づかずスルーかもしれないなーと感じますので、try/catchを使用することでそのようなミスも減りそうかなと思います(慣れの問題かもしれませんが)

Javascriptエンジンによっては早くなる

awaitについてJavascriptエンジンごとに独自の実装がされているので、Javascriptエンジンによる、パフォーマンスチューニングが可能なようです。
例えば、V8ではPromiseをそのまま使って処理したときよりも早くなるようです。

v8.dev

thenを使ったときと比べてネストが発生しない

ここはawaitの説明でもよく出てきますが、個人的には一つネストするくらいなら別に可読性は落ちないんじゃないかなーと考えています。
また、map関数もあったりするし、引数で匿名関数を渡すケースはJSにおいて普通にあることにも思えるので、このくらいいいのではないかなと思っています。

thenを使うとき

今まで、async/awaitの素晴らしさを書いたわけですが、以下のようなケースでは、thenを使ったほうがいいんじゃないかなーと個人的には思っています。

  • Promiseの後処理がかなり単純な場合
      • fetch APIのresponseを取るとき

また、以下のケースは問答無用でthenを使わなければならないです。

  • async funcの中以外で非同期処理を書く場合
    • 関数の中に書いていない場合(グローバル変数など)
    • 関数について、何らかの制約があり、asyncキーワードがかけない場合
  • ES5以前を使用している場合

それでは、個人的に考えているthenを使ったほうがいいケースについて書いていきます

Promiseの後処理がかなり単純な場合

例を見てみていただくのが一番わかり易いと思うので、書いてみます。
無駄な変数を定義しなくて良い分、読みやすく、意図もはっきりしたコードになっていると思います。

例 fetch APIやaxiosを使って取得したデータから必要な情報のみを取得する場合

thenで書いた場合

// Fetch APIを使った場合
const userId = fetch('http://example.com/api/user')
        .then(response => response.json())
        .then(data => data.id)

// axiosを使った場合
const groupId = axios.get('http://example.com/api/group')
        .then(response => response.data.id)

async/awaitで書いた場合

// Fetch APIを使った場合
const response = await fetch('http://example.com/api/user')
const user = await response.json()
const userId = user.id

// axiosを使った場合
const group = axios.get('http://example.com/api/group')
const groupId = group.data.id

まとめ

async/awaitとthen、使うならasync/awaitだが、thenで書いたほうがいい場合もある(個人的な見解です)

みなさんもご意見あれば、コメントいただけると嬉しいです!