ハイブリッドアプリを作っていて一番悩みやすいポイントは、非同期処理にあるのではないでしょうか。Web APIを使って外部からデータを取得したり、ファイルを書き込んだりする際には、この非同期処理が使われます。
async/awaitの登場によって大幅に書きやすくなっていますが、さらに改善は進んでいます。今回はfor await ofの書き方について解説します。
for await ofとは
forは皆さん使ったことがあるかと思います。whileとともに、繰り返し処理でよく使われる構文です。以下のような書き方が一般的でしょう。
1 2 3 |
for (let i = 0; i < 100; i++) { } |
この繰り返し処理の部分において非同期処理を有効にするのが for await of
構文になります。書き方としては次のようになります。
1 2 3 |
for await (obj of iteration) { } |
この for await of
の書き方をもう少し詳しく見ていきましょう。
イテレーションとは何か
上記コードで使われているiterationは、イテレーションと呼びます。配列のような形でオブジェクトを複数保持していて、呼ばれる度に次のオブジェクトを返却します。オブジェクトがなくなったら終了という仕組みです。
for await of
においても、forが自動的に次のデータ、次のデータと呼び出してくれることで、最終的にデータがなくなった段階で終了になります。
このイテレーションは、Generatorというオブジェクトが実体になります。Generatorオブジェクトは function*
という形で定義します。見た目は関数に似ています。
1 2 3 |
function* gen() { } |
そして、値を返す時には yield という構文を使います。例えばこんなジェネレーターを定義します。
1 2 3 4 5 6 |
function* gen() { yield 'Hello' yield 'World' yield 'From' yield 'Monaca' } |
これを使う際には、まず関数を実行します。
1 |
const g = gen() |
後は g.next()
で結果が返ってきます。
1 2 |
g.next() // {value: "Hello", done: false} |
さらにもう一度実行すると、結果が次のyieldに進みます。
1 2 |
g.next() // {value: "World", done: false} |
done: false
と書かれていますが、最後までいくと done: true
になります。これで繰り返し処理が終了したというのが分かります。ではこのジェネレーターfor
文に使ってみます。
1 2 3 |
for (let v of gen()) { console.log(v) } |
そうすると結果は次のようになります。関数をfor文の中で使い、まるで配列のように返せるのが分かるかと思います。
1 2 3 4 |
Hello World From Monaca |
非同期処理でどう使うのか
このジェネレータを使ったイテレーション処理において非同期処理をサポートしたのが for await of
構文になります。
今回は処理を非同期にするために、数秒間処理待ちする関数を作ります。通常、ここはファイル書き込みであったり、fetch関数でのネットワーク処理になります。
1 |
const sleep = (s) => new Promise(f => setTimeout(f, s * 1000)); |
そしてジェネレーターを定義するのですが、asyncを使うのがポイントです。そうすればジェネレーターの中でもawaitを使えます。
1 2 3 4 5 6 7 8 9 10 |
async function* gen_async() { await sleep(1) yield 'Hello' await sleep(5) yield 'World' await sleep(2) yield 'From' await sleep(1) yield 'Monaca' } |
そして呼び出し側で使うのが for await of
構文になります。
1 2 3 4 5 |
(async () => { for await (const v of gen_async()) { console.log(v); } })(); |
この処理は順番通り、数秒間処理待ちしながら実行されていきます。
1 2 3 4 |
Hello World From Monaca |
使い分け
非同期処理の実装としては、下記のような方法があるかと思います。
- コールバック
- Promise
- async/await
コールバックはCordovaプラグインでよく使われる記述方法ですが、現状では主流ではありません。コールバック方式をPromiseに置き換える方法もよく紹介されていますので、コールバック方式のまま利用することは多くないでしょう。
Promiseはthen/catchを使った方式になります。この方法を使った場合、コードのネストが深くなってしまうという難点があります。古いWebブラウザでも使えるので安心できる反面、ハイブリッドアプリにおいてはスマートフォンの最新Webブラウザ(のレンダリングエンジン)が対象になるので、そうした面は気にすることはないでしょう。
Promiseの利点としては、Promise.allを使うことで非同期処理が並列で実行できる点にあります。つまり複数の非同期処理(ネットワーク処理、画像の取得、複数ファイルへの書き込みなど)をまとめて実行できます。これによって処理の高速化が期待できます。
async/awaitを使うのは、コードの見通しがよくなり、非同期処理がまるで通常の同期処理のように書けるのが利点です。
難点としては、関数の呼び出し元で必ずasyncと定義しないといけないこと、さらに処理が直列になってしまうことでしょう。await処理が終わるまで次の処理が行われないので、forを使ったループ処理内でのasync/awaitはお勧めされていません。
まとめ
非同期処理を分かりやすく書くことができれば、アプリのコードがぐっとメンテナンスしやすくなり、保守や開発も容易になります。
ジェネレーターオブジェクトを覚えておくと、forを使った繰り返し処理などが綺麗に書けるようになるでしょう。そして、その中で非同期処理を使う場合には for await of
を使って書いてみてください。