そろそろ XHR を Fetch で置き換えれるようになってきた


みなさんの記憶には、 Fetch ではリクエストのキャンセルや progress が取れなく、 XHR を置き換えることはできないといったイメージがあるかと思います。

ですが、ブラウザは常に進化しています。既にリクエストもキャンセルできますし、 progress も取得可能です(対応ブラウザに限る)。

以下で紹介します。

リクエストのキャンセル

fetch() メソッドには第2引数に signal を渡せます。この signal と AbortController を使うことでリクエストのキャンセルが実現できるのです。

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort(), 10);

fetch('http://www.example.com/', {signal})
    .then(res => {
        return res.text();
    })
    .then(text => {
        console.log(text);
    })
    .catch(e => {
        if (e.name === 'AbortError') {
            console.error('The user aborted a request.');
        } else {
            console.error(e);
        }
    });

progress

これは XHR#onprogress を使っていた処理のことです。
Fetch では Streams API の ReadableStream を使って実現します。

fetch('<URL>')
    .then(res => {
        const total = parseInt(res.headers.get('content-length'), 10);
        let loaded = 0;

        return new Response(
            new ReadableStream({
                start(controller) {
                    const reader = res.body.getReader();

                    async function read() {
                        let result = await reader.read();
                        while (!result.done) {
                            const value = result.value;
                            loaded += value.byteLength;
                            console.log(`${loaded} / ${total}`);
                            controller.enqueue(value);
                            result = await reader.read();
                        }
                        controller.close();
                        return;
                    };
                    read();
                }
            })
        );
    })
    .then(res => res.blob())
    .then(data => console.log('download completed'))
    .catch(e => console.error(e));