[JavaScript] FileReader を使わない

元々は `Blob` や `File` の内容を読み込むのに `FileReader` が必要でしたが、最新版の JavaScript では不要です。

参考「[FileReader - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)」
参考「[Blob - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Blob)」
参考「[File - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)」

# 1. 読み込み

## 1.1. テキスト

### 1.1.1. UTF-8

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.readAsText(blob);

});

console.log(text);
```

```js:良い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await blob.text();

console.log(text);
```

### 1.1.2. UTF-8 以外

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.readAsText(blob, 'shift-jis');

});

console.log(text);
```

```js:良い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const arrayBuffer = await blob.arrayBuffer();

const textDecoder = new TextDecoder('shift-jis');
console.log(textDecoder.decode(arrayBuffer));
```

## 1.2. `ArrayBuffer`

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const arrayBuffer = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.readAsArrayBuffer(blob);

});

const textDecoder = new TextDecoder();
console.log(textDecoder.decode(arrayBuffer));
```

```js:良い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const arrayBuffer = await blob.arrayBuffer();

const textDecoder = new TextDecoder();
console.log(textDecoder.decode(arrayBuffer));
```

## 1.3. URL

`FileReader` を用いることでデータ URL を得られますが、データ URL を使用することによって様々な問題が発生する可能性があります。

特に理由がなければオブジェクト URL を使用する方が安全です。

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const url = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.readAsDataURL(blob);

});

console.log(url);
```

```js:良い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const url = URL.createObjectURL(blob);

console.log(url);

URL.revokeObjectURL(url);
```

パーセントエンコードまたは Base64 エンコードを用いてデータ URL を生成することも可能です。

```js
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await blob.text();
const url = 'data:text/plain,' + encodeURIComponent(text);

console.log(url);
```

```js
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const binaryString = uint8Array.reduce(
	(binaryString, uint8) => binaryString + String.fromCharCode(uint8),
	'',
);
const url = 'data:text/plain;base64,' + btoa(binaryString);

console.log(url);
```

参考「[データ URL - URI | MDN](https://developer.mozilla.org/ja/docs/Web/URI/Schemes/data)」
参考「[URL: createObjectURL() 静的メソッド - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/URL/createObjectURL_static)」
参考「[Base64 - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Base64)」
参考「[[JavaScript] Unicode 文字列やバイナリデータを Base64 エンコードおよびデコードする - Qiita](https://qiita.com/kerupani129/items/1d6b936974ec65ae4833)」

# 2. 読み込み中

`ReadableStream` を使用することで読み込み中の処理を行えます。

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.onprogress = event => {
		if ( event.lengthComputable ) {
			// 進捗状況
			console.log('%d / %d', event.loaded, event.total);
		}
	};

	reader.readAsText(blob);

});

console.log(text);
```

```js:良い例: チャンクごとの場合
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const readableStream = new ReadableStream({
	loaded: 0,
	total: blob.size,
	async start(controller) {
		const readableStream = await blob.stream();
		for await (const chunk of readableStream) {
			controller.enqueue(chunk);
			// 進捗状況
			this.loaded += chunk.length;
			console.log('%d / %d', this.loaded, this.total);
		}
		controller.close();
	},
});

const text = await new Response(readableStream).text();

console.log(text);
```

```js:良い例: 約 50ms ごとの場合
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const readableStream = new ReadableStream({
	loaded: 0,
	total: blob.size,
	intervalID: 0,
	progress() {
		// 進捗状況
		console.log('%d / %d', this.loaded, this.total);
	},
	async start(controller) {
		// 進捗状況
		this.intervalID = setInterval(() => this.progress(), 50);
		// 
		const readableStream = await blob.stream();
		for await (const chunk of readableStream) {
			controller.enqueue(chunk);
			// 進捗状況
			this.loaded += chunk.length;
		}
		controller.close();
		// 進捗状況
		clearInterval(this.intervalID);
		this.progress();
	},
	cancel() {
		// 進捗状況
		clearInterval(this.intervalID);
	},
});

const text = await new Response(readableStream).text();

console.log(text);
```

※ `FileReader` の progress イベントは約 50ms ごとに発生します。

参考「[ReadableStream - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/ReadableStream)」
参考「[6.2. The FileReader API - File API](https://w3c.github.io/FileAPI/#APIASynch)」(「約 50ms」)

# 3. 読み込み中止

`ReadableStream` を使用することで読み込みを中止出来ます。

```js:悪い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const text = await new Promise((resolve, reject) => {

	const reader = new FileReader();

	reader.onabort = () => resolve();
	reader.onerror = () => reject(reader.error);
	reader.onload = () => resolve(reader.result);

	reader.readAsText(blob);

	// 中止
	reader.abort();

});
```

```js:良い例
const blob = new Blob(['foo\n'], { type: 'text/plain' });

const readableStream = await blob.stream();

// 中止
await readableStream.cancel();
```

参考「[ReadableStream - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/ReadableStream)」。

# 4. 関連記事

参考「[イマドキな JavaScript で書かない・使わないもの: var, function, then, jQuery, その他 - Qiita](https://qiita.com/kerupani129/items/b2c3619856b048c13394)」