アップロード前にファイルが正常か簡易的にチェックする【JavaScript】

2023/06/08に公開

目的

  • ファイルサイズが0のファイルをアップロードされないようにする
  • 選択したファイルを、削除、移動、リネームした場合にエラーメッセージを出す

ファイルアップロード処理をライブラリなどに任せていると、自動でリトライ処理をしてくれていたりして、上記の状況で無限リトライになってしまったりします。
そうなる前に、事前にチェックするのがこの記事の目的です。

方法

FileReader を使用して、ファイルが読み込めるかチェックします。

  1. FileReader で読み込む前に file.size をチェックして、 0 ならエラーにする
  2. onerror が起きた場合は、 reject する
  3. onloadstarttotal0 なら reject する。 abort() を実行しすぐにファイルの読み込みを停止する
  4. onloadendresolve する(すでに reject していなければ)

コード


class FileEmptyError extends Error {}
  
const validateFile = (file) => {
  return new Promise((resolve, reject) => {
    const file_empty_error = new FileEmptyError(`選択されたファイル(${file.name})は、破損しているか、別のフォルダに移動された可能性があります。\n再度ファイルを選択してください。`);
    if (file.size === 0) {
      reject(file_empty_error);
      return ;
    }

    let rejected = false;

    // 読み込み可能なファイルかチェック
    const fr = new FileReader();
    const handle = (e) => {
      if (e.total === 0) {
        rejected = true;
        reject(file_empty_error);
      }
      else if (e.type === 'error') {
        rejected = true;
        reject(new Error('ファイルの読み込みに失敗しました。'));
      }

      // ファイルの読み込みをキャンセル
      fr.abort();
    };
    
    fr.onloadstart = handle;
    fr.onerror = handle;

    fr.onloadend = (e) => {
      if (rejected) return ;
      resolve();
    };

    fr.readAsArrayBuffer(file);
  });
};

補足

  • onloadstart は、エラー時は発火しません。そのため、 onerror でも処理しています。
  • onloadend は、エラーでもエラーじゃなくても、途中で読み込みを停止しても発火します。

実際にこの処理を使ったサンプル

https://runstant.com/simiraaaa/projects/before_upload_file_validation


<form onsubmit='upload(event)'>
  <div>
    <input type="file" name="file" value="" onchange='changeFile(event)' />
  </div>
  <button>送信</button>
</form>

const changeFile = (e) => {
  const file = e.currentTarget.files[0];
  if (file) {
    // File オブジェクトは size プロパティに初めてアクセスした時点のファイルサイズで固定される
    // 今回は、ファイルサイズ上限などのチェックをすることを想定して size に一度アクセスすることにする
    // (ブラウザによっては挙動が違うかもしれません)
    file.size;
  }
};

const upload = async (e) => {
  e.preventDefault();
  
  const file = e.target.elements.file.files[0];
  if (!file) alert('ファイルを選択してください');
  
  console.log('size:', file.size);
  try {
    await validateFile(file);
    alert('正常にアップロードできます');
  }
  catch (err) {
    alert(err);
  }
};

サンプルの説明

このサンプルでは、選択後のファイルが、0バイト、削除、移動、リネームされている場合にエラーメッセージを表示します。
コード中のコメントにもありますが、ファイルオブジェクトは、 size プロパティに初めてアクセスした時点でOSからファイルサイズを取得して、次回以降 size にアクセスした場合はその数値を返すようです。 (ブラウザや環境によって違うかもしれませんが)

あとがき

ファイルサイズチェックをしているはずなのに、0バイトのファイルがアップロードされていることがあり、その対策として実装しました。
file.size の挙動についても、なかなか気づけなかったので、何かしら参考になれば幸いです。

Discussion