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関数は非同期に実行されるので、引数を変えて順に呼び出すだけで、平行(並列ではありません)に動作します。

