1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【javascript】FileReaderのonloadが待てども待てども発火しなかった話、あと非同期処理

Last updated at Posted at 2022-07-07

はじめに

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.onloadonloadは、

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);

無事に動きます。
めでたしめでたし。

おわりに

結局大したことは書けていないというか、文章に改めて起こすと、ごく当たり前のことでした。
当たり前なんだけど、一旦自分で書いてみないと、案外わからないものですね。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?