【Nuxt.jsで近くのお店を探すアプリを作成】#4 async/await

Web

今回の目標

前回は現在位置の取得について説明し、実際に取得した位置をグルメサーチAPIに送信してお店の一覧を取得しました。

【Nuxt.jsで近くのお店を探すアプリを作成】#3 現在位置の取得
近くのお店を検索するために、ブラウザから現在位置を取得する方法を紹介します。現在位置を取得したら、前回紹介した方法でホットペッパーのAPIからお店情報の一覧を取得してみましょう。

今回はこの現在位置の取得とAPIの送信の部分にもう少し手を加えていきます。

何が問題なのか

例えば次のようにしてみましょう。

mounted() {
  //現在位置を取得
  let pos = undefined 
  navigator.geolocation.getCurrentPosition((position) => {
    pos = position
  }, this.setError)
  //APIからデータを取得
  this.$axios.get('http://localhost:3000/api/gourmet/v1/', {
    params: { 
      key: process.env.apikey,
      lat: pos.coords.latitude,
      lng: pos.coords.longitude,
      format: 'json' 
    } 
  }).then(this.setShop).catch(this.setError)
}

これはうまく動作しません。なぜかというとnavigator.geolocation.getCurrentPosition()は非同期処理のため、結果を待たずしてAPIへの送信が行われるからです。

現在位置の取得⇒グルメサーチAPIへの送信という順番を守らなければこの処理は成立しません。つまり同期処理にする必要があります。

前回に説明しましたが、結果取得後の処理は第一引数にコールバック関数として定義します。

さてこのような場合が何回も続く場合どうなるでしょうか。例えばAPIが3つ(A、B、C)があり、順番通りに送信しなければいけない場合、このようになります。

this.$axios.get('http://localhost:3000/api/A').then((resA) => {
  //API A のレスポンス後の処理
  this.$axios.get('http://localhost:3000/api/B').then((resB) => {
    //API B のレスポンス後の処理
    this.$axios.get('http://localhost:3000/api/C').then((resC) => {
      //API C のレスポンス後の処理
    }).catch((err) => {
      //API C のエラー処理
    })
  }).catch((err) => {
    //API B のエラー処理
  })
}).catch((err) => {
  //API A のエラー処理
})

見ていただいてわかるように、ネストが深くなり可読性はかなり落ちます。このような非同期処理をより簡潔にコーディングできるようにするための仕組みが、今回説明するasync/awaitです

Promiseについて

async/awaitの説明の前にPromiseについて説明をしておきます。

MDNには、「Promiseオブジェクトは非同期処理の最終的な完了処理(もしくは失敗)およびその結果の値を表現します。」と書かれています。要は、Promiseを使えば非同期処理ができますよ、ということです。(ぇ

もう少し補足をしておくと、Promiseは非同期処理の完了または失敗を表すオブジェクトです。

実際のところaxiosはPromiseオブジェクトを返しています。つまり成功時の処理であるthenと失敗時の処理であるcatchはPromiseオブジェクトのメソッドなのです

と言われてもよくわからないと思うので、実際にPromiseを使って非同期処理を定義してみます。

Promiseによる非同期処理

では実際に非同期処理を定義していきます。

const asyncProcess = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Hello')
    }, 3000)
  })
}

先ほど説明したようにPromiseはオブジェクトのため、コンストラクタにより生成します。コンストラクタの引数には、非同期で実行したい処理(関数:function)を渡します。またその関数は引数に「resolve」と「reject」を持ちます。

resolveは処理を成功とする関数で、rejectは処理を失敗とする関数です。resolveかrejectが実行された時点で処理は終了します。

今回定義した処理は単純で、3秒後に処理成功のresolveを実行しています。

Promiseによる非同期処理を行う場合は次のようにします。

asyncProcess().then((res) => {
  console.log(res)
}).catch((err) => {
  console.log(err)
})

実行すると3秒後に「Hello」とコンソールに表示されるはずです。

先ほどaxiosのところで、thenは成功時の処理で、catchは失敗時の処理というように説明しましたが、正確には違います

thenにはresolveが実行された場合の処理を設定し、引数の「res」にはresolveの引数が受け渡されます。同様にして、catchにはrejectが実行された場合の処理を設定し、引数の「err」にはrejectの引数が受け渡されます

今回の場合は3秒後にresolveを引数「Hello」で実行しているため、thenの処理内のconsole.log(res)が実行されコンソールに「Hello」と表示されます。

動作確認のために次のように似た関数を作って実行してみてください。少し動きがつかめると思います。

const asyncProcess = () => { 
  return new Promise((resolve, reject) => { 
    setTimeout(() => { 
      resolve('resolve') 
    }, 3000) 
  }) 
}

const asyncProcess2 = () => { 
  return new Promise((resolve, reject) => { 
    setTimeout(() => { 
      reject('reject') 
    }, 3000) 
  }) 
}

const asyncProcess3 = () => { 
  return new Promise((resolve, reject) => { 
    setTimeout(() => { 
      console.log('nothing')
    }, 3000) 
  }) 
}

現在位置取得のPromise化

これから説明するasync/awaitを使用する場合、Promiseである必要であるため現在位置の取得処理をPromiseを使った非同期処理に置き換えます。

const getCurrentPosition = () => {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject)
  })
}

少しわかりにくいかもしれませんが、これは次と同義になります。

const getCurrentPosition = () => {
    return new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition((position) => {
            resolve(position)
        }, (err) => {
            reject(err)
        })
    })
}

実行すると「res」にPositionオブジェクトが設定されていることがわかります。

getCurrentPosition().then((res) => {
  console.log(res)
})

async/await

async/awaitは、Promiseによる非同期処理をより簡潔にコーディングするための仕組みです。この記事ではawaitの説明のみ行います。そのほかの説明は割愛します。

先にお店の一覧の取得処理をasync/awaitを使って書き換えてみます。

async mounted() {
  try {
    //現在位置の取得
    const position = await getCurrentPosition()
    //APIからデータを取得
    const { data } = await this.$axios.get('http://localhost:3000/api/gourmet/v1/', {
      params: {
        key: process.env.apikey,
        lat: position.coords.latitude,
        lng: position.coords.longitude,
        format: 'json'
      }
    })
    //店の一覧を設定
    this.shops = data.results.shop
  } catch(err) {
    this.setError(err)
  }
}

コードがより直感的になったのではないでしょうか。

本来getCurrentPositionとaxiosは非同期処理ですが、前にawaitをつけることで同期的に処理されます。少し言い換えると、Promiseオブジェクトの前にawaitをつけることでresolveされるまで待機するようになりますそしてその返値としてresolveの結果を受け取ります

よって今回のこのコードは、現在位置を取得してからグルメサーチAPIに送信するという順番を守って実行されます。

エラーハンドリングについては個々で行うこともできます。が、エラーが発生したからと言って処理は中断しないので、意図的に中断させる必要があります。

const position = await getCurrentPosition().catch(this.setError)
if (this.error) {
  return
}

今回のようにtry/catchでまとめることもできるので状況に応じて使い分けましょう。

最後に、awaitはasync function内でのみ使用できます。async functionについての説明は割愛しますが、関数の前、今回の場合mountedの前にasyncを宣言することでasync functionとすることができます。

まとめ

  • 非同期処理はPromiseオブジェクトによって定義できます
  • 成功の場合はresolve、失敗の場合はrejectを実行します
  • async/awaitによって非同期処理を同期的に処理することができます

↓↓↓次回↓↓↓

【Nuxt.jsで近くのお店を探すアプリを作成】#5 asyncData
Nuxt.jsで外部からデータを取得する場合に、asyncDataを使うとdataと同期が取れて便利になります。今回はasyncDataの基本的な使い方を説明し、詳細ページのデータ取得処理を実装していきます。

 

- Nuxt.jsのおすすめ書籍はコチラ -

コメント

タイトルとURLをコピーしました