FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

ネットワーク処理を伴うループを高速化するには

f:id:mbaasdevrel:20180913163950p:plain

一覧データを表示したり、さらにそこから関連データを表示する際など、ループ処理(繰り返し処理)の中でネットワーク処理を行うことはよくあります。しかし、各ネットワーク処理が1秒で終わるとしても、10回ループすると10秒かかってしまいます。そうした処理の累積時間はユーザにとってはストレスで、アプリ離れを引き起こします。

今回はネットワーク処理が伴うループ処理を高速化する方法と、実際の測定結果を紹介します。

処理の書き方は3つ

ネットワーク処理を伴うループの書き方は全部で3つあります。

  • async/awaitを使う
  • Promiseを使う
  • Promise.allを使う

これらを上から順番に紹介します。今回はJavaScript SDKを使いますが、他のプログラミング言語でもasync/awaitが一般的になってきているので、それほど変わらないはずです。

async/awaitを使う

サンプルのコードです。1〜3は実行される順番です。ループ処理が終わるまでは3が実行されないので、async/awaitは分かりやすいコードが書けます。

const start = new Date
const photos = await Photo.fetchAll()
// 1
for (let photo of photos) {
  const data = await ncmb.File.download(photo.get('fileName'), 'blob')
  console.log(data)
  // 2
}
// 3
const end = new Date
console.log(end.getTime() - start.getTime())

この場合の処理時間(データは4件)は7.156sでした。

Promiseを使う

サンプルのコードです。ネットワーク処理は非同期なので、ループ内の処理が完了する前にforを抜けてしまいます。この点がasync/awaitとの違いになります。全件処理したかどうかが分からないので、処理件数のカウントを取っておき、その数をもって全件処理済みかどうかを判断しています。

const start = new Date
const photos = await Photo.fetchAll()
let count = 0
// 1
for (let photo of photos) {
  ncmb.File.download(photo.get('fileName'), 'blob')
    .then(blob => {
      // 3
      count++
      console.log(photo.get('fileName'), blob.length)
      if (count === photos.length) {
        const end = new Date
        console.log(end.getTime() - start.getTime())
      }
    })
}
// 2

この処理の場合、3.476sでした。データ数や転送量によりますが、処理がasync/awaitの場合に比べて2倍以上高速化しています。

Promise.allを使う

サンプルのコードです。ファイルダウンロード処理をPromise.allでまとめて処理しています。その結果、ダウンロード処理の並列化と、その後のforループ処理の同期化が実現できます。Promiseを使った時にはforループをその中の処理が終わる前に抜けてしまっていましたが、この場合はすべての処理が終わってから行われるようになります。

const start = new Date
const photos = await Photo.fetchAll()
// 1
const data = await Promise.all(
  photos.map(photo => ncmb.File.download(photo.get('fileName'), 'blob'))
)
// 2
for (let index in data) {
  // 3
  const photo = photos[index]
  const file = data[index]
  console.log(photo.get('fileName'), file.length)
}
// 4
const end = new Date
console.log(end.getTime() - start.getTime())

この処理の場合、時間は3.726sでした。処理としてはPromiseを単純に使った場合と同じなので、0.3sは誤差といえるでしょう。

まとめ

ネットワーク処理は何度も行うと時間がかかってしまいます。なるべくデータを一回で取れるようにしたり、処理を並列化する工夫が必要です。今回のように簡単なコードでも2倍以上高速化されます。アプリ開発の際にはこうした点に注意して実装してください。

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。