JavaScriptが、だれでも簡単に動的なHTMLコンテンツを作成できる「ちょっとした道具」であった時代は遠い過去となり、今では本格的なアプリケーションを実現できるレベルの「強力な道具」にまで発展してきました。
ネットワークアクセスはもとより、ローカルファイルへのアクセスやあまつさえデータベースまで!
そして、それらは全て、非同期非同期非同期非同期非同期!!!!!!!
「いいかげんにしてくれ〜〜〜〜!!」
幸いというか、世界中に同じ様に感じた人たちが沢山いたと見えて、解決策が提示されています。
そのあたりをちょっと紹介します。
やりたいことはこうです。
XMLHttpRequest
でリクエストし、その結果をJSONとして評価してfileNameプロパティよりファイル名を取得します。
そのファイル名に再度リクエストを行い、その結果をイメージのデータURLとして画面に表示します。
非同期呼び出しの嵐です。
背景色が変わっている箇所がイベントリスナの登録箇所ですが、あくまでもこれは正常ケースのみです。
本来であれば、ここにまだ"error"のイベントリスナ登録(場合によっては、"abort"のイベントリスナ登録も)と、HTTPステータスが200番代以外のエラーケースを追加する必要があります。
ちょっとうんざりですね。
まだ、処理が分割できればよいのですが、普通にコーディングするとネストしてしまうので、解りやすく分割するのは結構厄介です。
一つ前のFetchの章で登場したPromise
ですが、そもそもこの様な非同期ネスト地獄(callback hell:コールバック地獄というらしい)をなんとかしようと考えられたものです。
なんとかしてもらいましょう。
Promiseチェーンに関しては前の章でも簡単に照会しましたが、コードが解りやすくも見やすくもならないので(個人の感想です)、ここでは非同期関数とawait演算子により実装します。
XMLHttpRequest
は、Promise
による実装としてFetch
が利用できますが、FileReader
は代替手段が存在しないので作成します。共通関数として汎用的に利用できるように実装します。
上記の関数は、FileReader
の処理をPromise
でラップした関数で、引数で渡されたblob
をDataURL
に変換して返します(実際の返却値は、DataURL
をラップしたPromise
になります)。
処理が成功した時にresolve
(解決)、エラーが発生した時にreject
(拒否)をそれぞれ呼び出しています。
resolve
が呼び出されると、resolve
に渡した引数の内容がPromise
でラップされて返却されます。
reject
が呼び出されると、reject
に渡した引数の値がスローされます。
下記が、非同期関数の本体です。
関数宣言の前にasync
を付けることで、非同期関数となります。
非同期関数(async
付きの関数)の中でawait
演算子を付けてPromise
を返す関数を実行すると、resolve
かreject
のいずれかが呼び出されるまで、処理を停止します。
3行目でfetch
を使ってサーバにリクエストしています。await
を付けているので、レスポンスが返るまで処理が停止します。
ネットワークエラーなどが発生した場合は、この呼び出しでエラーがスローされます。
レスポンスが返ると、4行目で結果を確認します。HTTPステータスが200番代で返るとresponse.ok
がtrue
となるので、200番代以外のステータスが返った場合にエラーをスローします。
7行目でレスポンスからjson
オブジェクトを取得しています。
response.json()
はjson
オブジェクトをラップしたPromise
を返すので、await
を付けて呼び出すことで、json
オブジェクトそのものを取得します。
それ以降は、ほぼ同じ処理の繰り返しですが、14行目で先に定義した共通関数(readAsDataURL
)を呼び出しています。
全体をtry〜catch
で囲っていますが、このcatch
節で非同期処理の中で発生したエラーを含め、全てのエラーがキャッチできます。
先のXMLHttpRequest
を使ったサンプルと同じことを実装している上に、エラー処理も取り込んでいる割には簡潔に記述できていると思うのですが、いかがでしょう。
Promise
・async
・await
のおかげで楽になった処理に、スリープがあります。特にループ内で一定時間スリープさせる処理を、簡単に記述できるようになりました。
先ずは、setTimeout
をPromise
でラップした関数を用意します。
スリープを目的としているので、第2引数はオプションです。指定されている場合にも呼び出します。
これを使って、簡単な追いかけっこプログラムを作ってみました。
殆ど説明する箇所はありませんね。ループの中で、プレイヤー要素の左側のマージンを大きくしているだけです。
大きくするマージンの量は、疑似乱数を発生させて決定しています。
15行目と17行目で先に作ったsleep
関数を呼び出しています。今回は一定時間処理をスリープさせることが目的なので、引数は1つです。
chase
関数は非同期に実行されるので、引数を変えて順に呼び出すだけで、平行(並列ではありません)に動作します。