はじめに
javascriptで、FileReaderオブジェクトを使ってアップロードしたファイルのプレビュー機能を作っているときに、onload処理が待てども待てども発火しないという事件がありました。
この機能の開発は、基本的に他の場所にある機能の移植で終わる物だったので、特に調べもせず書き進めた結果です。
ハマって観念して調べたその過程で、非同期処理がどういうものなのかストンと腑に落ちたので、アウトプットします。
結論
結論から書くと、ハマった原因は入れ子違いでした。あー。。。
問題のコード
// インスタンス作成
const reader = new FileReader();
// 頑なに発火しないonloadの関数
// 正常に終わった時の処理
reader.onload = e => {
let profileImage = e.target.result;
};
// エラー処理
reader.onerror = function(e) {
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('ファイルが見つかりません。');
break;
case e.target.error.NOT_READABLE_ERR:
alert('ファイルが読み込めません。');
break;
case e.target.error.ABORT_ERR:
break;
default:
alert('ファイルの読み込みに失敗しました。');
}
// キャンセル処理
reader.onabort = function (e) {
alert('ファイルの読み込みがキャンセルされました。');
};
// 読み込み操作
reader.readAsDataURL(file);
};
お気づきでしょうか。
こうやって抜き出すと気付きやすいんですがね、、、
ハマっていると視野がすごく狭くなるもので、気づかないままデバッグを始めます。
デバッグをしてみる
console.log("beforeOnload");
reader.onload = e => {
console.log("onload");
let profileImage = e.target.result;
};
console.log("afterOnload");
// 省略
こんな感じで書き入れて動かしてみると、コンソールには
beforeOnload
afterOnload
とだけ。
そもそもonloadの関数に入っていないようです。
観念して、そもそもonloadがどんなイベントを拾うのかわかっていなかったのでちゃんと調べることに。
そもそもonloadはどんなイベントのハンドラなのか
上記リンクによると、FileReader.onload
のonload
は、
FileReader.onload
load イベントのハンドラです。このイベントは、読み込み操作が正常に完了するたびにトリガされます。
とのこと。
ついでに、今回他の場所で使っているonerror
は
FileReader.onerror
error イベントのハンドラです。このイベントは、読み込み操作がエラーになるたびにトリガされます。
で、onabort
は
FileReader.onabort
abort (en-US) イベントのハンドラです。このイベントは、読み込み操作が中止されるたびにトリガされます。
とのこと。
どれもイベントハンドラなので、各イベントが起きなければ、そこで指定した関数も動きません。
そして今回で言うと、読み込み操作は最下段の
reader.readAsDataURL(file);
です。
です、、、?
oh、、、
変なところで入れ子になってる
と言うことで、いろいろと動いてくれていなかったのは全て、読み込み操作がイベントハンドラの中に書かれていたせいでした。
今回のケースだと、読み込み操作はonerrorが発火した際に動く関数の中で書かれており、
- onerrorの処理は読み込み操作でエラーが起こらないと発火しない
- その読み込み操作は、読み込み操作でエラーが起きないと動作しない
と言う謎の睨み合いが起きた結果(というかそのように書かれた結果)、動かなかったわけです。
ところで非同期処理
ところで、先ほどのデバッグのコード、
console.log("beforeOnload");
reader.onload = e => {
console.log("onload");
let profileImage = e.target.result;
};
console.log("afterOnload");
// 省略
// 読み込み操作
reader.readAsDataURL(file);
ちゃんと動いてくれると、コンソールには
beforeOnload
afterOnload
onload
と表示されます。
onloadの中は最初は素通りされ、読み込み処理が終わった後、上に戻ってきてonloadで指定した処理が走っている格好です。
非同期処理といえば、私が最近よく使うのはAxiosです。Vue.jsで非同期通信を行いたいときに、スタンダードに使われているやつですが、そういえばその中で書くthenやcatchの処理も、
-
then
は通信が終わった後 -
catch
は通信やthenの中の処理でエラーが起きた後
でないと、動きません。
非同期処理という言葉とその意味は今までも知識としては頭にあったけど、それがつながった瞬間でした。
正しいコード
いろいろつらつら書いてきましたが、とりあえず動いてくれるコードはこちら。
const reader = new FileReader();
reader.onload = e => {
let profileImage = e.target.result;
};
reader.onerror = function(e) {
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('ファイルが見つかりません。');
break;
case e.target.error.NOT_READABLE_ERR:
alert('ファイルが読み込めません。');
break;
case e.target.error.ABORT_ERR:
break;
default:
alert('ファイルの読み込みに失敗しました。');
}
};
reader.onabort = function (e) {
alert('ファイルの読み込みがキャンセルされました。');
};
reader.readAsDataURL(file);
無事に動きます。
めでたしめでたし。
おわりに
結局大したことは書けていないというか、文章に改めて起こすと、ごく当たり前のことでした。
当たり前なんだけど、一旦自分で書いてみないと、案外わからないものですね。