⏫
アップロード前にファイルが正常か簡易的にチェックする【JavaScript】
目的
- ファイルサイズが0のファイルをアップロードされないようにする
- 選択したファイルを、削除、移動、リネームした場合にエラーメッセージを出す
ファイルアップロード処理をライブラリなどに任せていると、自動でリトライ処理をしてくれていたりして、上記の状況で無限リトライになってしまったりします。
そうなる前に、事前にチェックするのがこの記事の目的です。
方法
FileReader
を使用して、ファイルが読み込めるかチェックします。
-
FileReader
で読み込む前にfile.size
をチェックして、0
ならエラーにする -
onerror
が起きた場合は、reject
する -
onloadstart
でtotal
が0
ならreject
する。abort()
を実行しすぐにファイルの読み込みを停止する -
onloadend
でresolve
する(すでに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
は、エラーでもエラーじゃなくても、途中で読み込みを停止しても発火します。
実際にこの処理を使ったサンプル
<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