[Javascript]大容量ファイルの読み込み時にFileReaderでエラーが発生する

10年以上前に作られたアプリケーションの保守で
ファイルのドラッグ＆ドロップでファイルサイズが大きいとエラーと表示されるという指摘をいただき調査をした。

# 対象のソース
```html:sample.html
<!DOCTYPE html>
<html lang="ja">
	<head>
	<meta charset="utf-8">
	<title>ドラッグ&ドロップ</title>
	<link rel="stylesheet" href="dragAndDrop.css">
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
	<script src="dragAndDrop.js"></script>
	</head> 
	<body>
		<h1>ドラッグ&ドロップ　サンプル</h1>
		<div id="disp_area">
			<div id="drop_area" class="drop_area">
				ここにファイルをドロップしてください。
			</div>
			<input type="button" class="btn btn-default"
					onclick="clearFile()" value="クリア" />
		</div>
	</body>
</html>
```

```dragAndDrop.css
.drop_area
{
	font-weight: bold;
	text-align: center;
	padding: 1em 0;
	margin: 1em 5px;
	color: #555;
	border: 2px dashed #555;
	border-radius: 7px;
	cursor: default;
	width: 500px;
	background-color: #FFFFFF;
}
.drop_area_hover
{
	color: #f00;
	border-color: #f00;
	border-style: solid;
	box-shadow: inset 0 3px 4px #888;
}
#disp_area
{
	padding: 10px 10px;
	margin: 1em 0;
}
```

```dragAndDrop.js
// ドロップ時のアクション
function fileRead(event) {
	// ドロップしたファイル
	var files = event.originalEvent.dataTransfer.files;

	// 表示エリアのクリア
	var objDispArea = $("#disp_area");
	objDispArea.innerHTML = '';

	// 表示切り替え要素
	var targetElement = $(event.target);

	// ドロップされたファイルの処理
	for (var i = 0; i < files.length; i++) {
		var file = files[i];
		var objFileReader = new FileReader();
		
		objFileReader.onerror = function(e) {
			console.log("error_code:"+e.target.error.code);
			switch (e.target.error.code) {
				case e.target.error.NOT_FOUND_ERR:
					alert('ファイルが見つかりません。');
					break;
				case e.target.error.SECURITY_ERR:
					alert('セキュリティーエラーです。');
					break;
				case e.target.error.NOT_READABLE_ERR:
					alert('ファイルが読み込めません。');
					break;
				case e.target.error.ABORT_ERR:
					alert('ファイルの読み込みが中断されました。');
					break;
				case e.target.error.NOT_READABLE_ERR:
					alert('ファイルの読み込み権限がありません。');
					break;
				case e.target.error.ENCODING_ERR:
					alert('サイズ制限を超えています。');
					break;
				default:
    				alert('ファイルの読み込みに失敗しました。');
			}
			targetElement.text('【' + file.name + '】 ファイル読み込み時にエラーが発生しました。');
			return;
		};

		objFileReader.onload = (function(e) {
			targetElement.text(file.name);

			// サーバ側でファイルの処理をする場合ここでAPI呼び出すなどする
			// var formData = new FormData();
			// formData.append('uploadFile', file);
			// $jq.ajax('/xxx/fileUpdate', {
			// 	method : 'POST',
			// 	contentType : false,
			// 	processData : false,
			// 	data : formData,...
			
		})(file);
		objFileReader.readAsArrayBuffer(file);

	}
	preventDefault(event);
}

// ドロップ時処理
function dropEvent(event) {
	fileRead(event);
	fileDragHover(event);
}
// ドラッグオーバー時処理
function dragoverEvent(event) {
	preventDefault(event);
	fileDragHover(event);
}
// ドラッグエンター時処理
function dragenterEvent(event) {

}
// ドラッグリーブ時処理
function dragleaveEvent(event) {
	fileDragHover(event);
}
// ドラッグホバー時処理
function fileDragHover(event) {
	if (event.type == "dragover") {
		$(event.target).addClass('drop_area_hover');
	} else {
		$(event.target).removeClass('drop_area_hover');
	}
}

// ブラウザが実装している処理を止める
function preventDefault(event) {
	event.stopPropagation();
	event.preventDefault();
}

// ロード時処理
function doloadEvent() {
	// ドロップ領域外のドラッグ&ドロップイベントを許可しない
	$("html").on("drop dragenter dragover", function(event) {
		preventDefault(event);
	});

	$(".drop_area").each(function() {
		// ドロップエリア
		var objDropArea = $(this);
		if (window.File && window.FileReader) {
			// ドロップ、ドラッグエンター、ドラッグオーバー、ドラッグリーブ時のアクションを設定
			objDropArea.on({
				"drop" : dropEvent,
				"dragenter" : dragenterEvent,
				"dragover" : dragoverEvent,
				"dragleave" : dragleaveEvent
			});
		} else {
			// ブラウザが対応していない場合の処理
			objDropArea.text('お使いのブラウザは対応していません。');
			var objDispArea = document.getElementById("disp_area");
			objDispArea.parentNode.removeChild(objDispArea);
		}
	});

}

// ロード時イベント設定
if (window.addEventListener) {
	window.addEventListener("load", doloadEvent, false);
} else if (window.attachEvent) {
	window.attachEvent("onload", doloadEvent);
} else if (window.onLoad) {
	window.onload = doloadEvent;
}


// ドロップエリアの動画をクリアする
function clearFile() {
	$("#drop_area").text("ここにファイルをドロップしてください。");
}
```

# 原因調査
1GB程度までのファイルであれば問題なく動くのだが2GBくらいになるとonerrorに入ってきます。
エラーコードで処理を分ければ良いかと考えたのですが想定される返却されるコードどれにも該当しない0が返却されてきます。謎…。

・エラーコード一覧
|コード|定数|内容|
|:-:|:-|:-|
|1|NOT_FOUND_ERR|ファイルが見つからない|
|2|SECURITY_ERR|セキュリティエラー|
|3|ABORT_ERR|ファイルの読み込みが中断された|
|4|NOT_READABLE_ERR|ファイルの読み込み権限がない|
|5|ENCODING_ERR|サイズ制限を越えた|

# 対応
かつてはファイル読み込みにFileReaderを使うことが必須だったのですが現在では必要なくなっているとのことでfileReadの処理でFileReaderを使わない形に修正しました。
こちらの形でエラーが発生することはなくなりサーバ側にも問題なく2GB超のファイルを送ることができました。

```dragAndDrop.js
function fileRead(event) {
	// ドロップしたファイル
	var files = event.originalEvent.dataTransfer.files;

	// 表示エリアのクリア
	var objDispArea = $("#disp_area");
	objDispArea.innerHTML = '';

	// 表示切り替え要素
	var targetElement = $(event.target);

	// ドロップされたファイルの処理
	for (var i = 0; i < files.length; i++) {
		
		var file = files[i];
		targetElement.text(file.name);
		
		// サーバ側でファイルの処理をする場合ここでAPI呼び出すなどする
		// var formData = new FormData();
		// formData.append('uploadFile', file);
		// $jq.ajax('/xxx/fileUpdate', {
		// 	method : 'POST',
		// 	contentType : false,
		// 	processData : false,
		// 	data : formData,...
	}
}
```

# 参考記事
https://qiita.com/kerupani129/items/121b5f9ae14fa173445e