中止可能な取得

「取得の中止」に関する GitHub の当初の問題は、 2015 年にオープンしました2017 年(現在の年)から 2015 年を取ると、2 になります。これは、 2015 年は実は「永久」だったので、できます。

2015 年は、進行中のフェッチ処理を中止することについて初めて検討し始めました。GitHub のコメントが 780 件もあった後、 誤起動が 2 回、pull リクエストが 5 回あったら、ついにブラウザでのフェッチ ランディングの中断、 1 つ目は Firefox 57 です

更新: 不正解です。Edge 16 ではまず abort サポートが導入されました。おめでとうございます! エッジチーム

歴史については後で詳しく見ていきますが、まずは API:

コントローラ + 信号操作

AbortControllerAbortSignal を紹介します。

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

コントローラのメソッドは 1 つだけです。

controller.abort();

これを行うと、次のようにシグナルを通知します。

signal.addEventListener('abort', () => {
    // Logs true:
    console.log(signal.aborted);
});

この API は DOM 標準で規定されており、それが API 全体となります。です。 他のウェブ標準や JavaScript ライブラリで使用できるようにするため、汎用的なものにする必要があります。

シグナルを中止してフェッチする

取得には AbortSignal かかることがあります。たとえば、5 秒経過後にフェッチ タイムアウトを設定する方法は次のとおりです。 秒:

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

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

fetch(url, { signal }).then(response => {
    return response.text();
}).then(text => {
    console.log(text);
});

取得を中止するとリクエストとレスポンスの両方が中止されるため、レスポンス本文の読み取りはすべて (response.text() など)も中止されます。

デモ – 執筆時点では、 Firefox 57 がサポートされています。気をつけて。デザインスキルを持つ人は誰も関わっていませんでした。 重要です

または、シグナルをリクエスト オブジェクトに渡して、後でフェッチのために渡すこともできます。

const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });

fetch(request);

request.signalAbortSignal であるため、これは機能します。

取得の中止への対応

非同期オペレーションを中止すると、Promise は AbortError という名前の DOMException で拒否されます。

fetch(url, { signal }).then(response => {
    return response.text();
}).then(text => {
    console.log(text);
}).catch(err => {
    if (err.name === 'AbortError') {
    console.log('Fetch aborted');
    } else {
    console.error('Uh oh, an error!', err);
    }
});

ユーザーが操作を中止した場合、エラー メッセージを表示することはあまりおすすめしません。これは、 「error」ユーザーが要求したとおりに実行できたかどうかを確認しますこれを回避するには、 特に abort エラーを処理する方法を紹介します。

ユーザーにコンテンツの読み込みボタンと中止ボタンを表示する例を次に示します。フェッチが 中止エラーでなければ、エラーが表示されます。

// This will allow us to abort the fetch.
let controller;

// Abort if the user clicks:
abortBtn.addEventListener('click', () => {
    if (controller) controller.abort();
});

// Load the content:
loadBtn.addEventListener('click', async () => {
    controller = new AbortController();
    const signal = controller.signal;

    // Prevent another click until this fetch is done
    loadBtn.disabled = true;
    abortBtn.disabled = false;

    try {
    // Fetch the content & use the signal for aborting
    const response = await fetch(contentUrl, { signal });
    // Add the content to the page
    output.innerHTML = await response.text();
    }
    catch (err) {
    // Avoid showing an error message if the fetch was aborted
    if (err.name !== 'AbortError') {
        output.textContent = "Oh no! Fetching failed.";
    }
    }

    // These actions happen no matter how the fetch ends
    loadBtn.disabled = false;
    abortBtn.disabled = true;
});

デモ – 執筆時点では、 Edge 16 と Firefox 57 に対応しています。

1 つのシグナルで多数のフェッチ

1 つのシグナルを使用して、多数のフェッチを一度に中止できます。

async function fetchStory({ signal } = {}) {
    const storyResponse = await fetch('/story.json', { signal });
    const data = await storyResponse.json();

    const chapterFetches = data.chapterUrls.map(async url => {
    const response = await fetch(url, { signal });
    return response.text();
    });

    return Promise.all(chapterFetches);
}

上記の例では、最初のフェッチと並列チャプターに同じシグナルが使用されています。 なります。fetchStory の使い方は次のとおりです。

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

fetchStory({ signal }).then(chapters => {
    console.log(chapters);
});

この場合、controller.abort() を呼び出すと、進行中の取得がすべて中止されます。

今後について

その他のブラウザ

Edge はこれを最初に出荷するのに非常に役立っており、Firefox もその調子で進んでいます。同社のエンジニア テストスイートから実装され、 表示されます。その他のブラウザの場合は、以下のチケットを利用してください。

Service Worker の場合

Service Worker のパーツの仕様を完成させる必要がありますが、プランは次のとおりです。

前述のように、すべての Request オブジェクトには signal プロパティがあります。Service Worker では fetchEvent.request.signal は、ページがレスポンスに興味を失った場合に中止を通知します。 そのため、次のようなコードはそのまま機能します。

addEventListener('fetch', event => {
    event.respondWith(fetch(event.request));
});

ページで取得が中止された場合、fetchEvent.request.signal は中止を通知します。したがって、 Service Worker も中断されます。

event.request 以外を取得する場合は、シグナルを できます。

addEventListener('fetch', event => {
    const url = new URL(event.request.url);

    if (event.request.method == 'GET' && url.pathname == '/about/') {
    // Modify the URL
    url.searchParams.set('from-service-worker', 'true');
    // Fetch, but pass the signal through
    event.respondWith(
        fetch(url, { signal: event.request.signal })
    );
    }
});

仕様に沿ってこれをトラッキングします。リンクを追加します。 ブラウザ チケットを転送できます。

歴史

そうですね...比較的シンプルなこの API が完成するまでに長い時間がかかりました。その理由は次のとおりです。

API の不一致

ご覧のとおり、GitHub の説明はかなり長くなっています。 このスレッドには多くのニュアンスがあり、ニュアンスの欠如もありますが、主な相違点は 1 つです。 グループは、fetch() から返されたオブジェクトに abort メソッドが必要でしたが、もう 1 つは、 反応を得ることと反応に影響を与えることとを分けることを望んでいました。

これらの要件に互換性がないため、1 つのグループでは要件を満たせませんでした。もし ごめんね!気分が良くなるのなら、私もそのグループに入っていました。しかし、AbortSignal を確認することは、 他の API の要件を考慮すると、これが適切な選択であるように見えます。また、チェーンされた Promise を 不可能ではないにしても、非常に複雑になります。

応答だけでなく中止もできるオブジェクトを返すには、代わりに シンプル ラッパー:

function abortableFetch(request, opts) {
    const controller = new AbortController();
    const signal = controller.signal;

    return {
    abort: () => controller.abort(),
    ready: fetch(request, { ...opts, signal })
    };
}

TC39 での誤開始

キャンセルされたアクションをエラーとは区別しようとしていた。これには 3 つ目の約束が含まれます 「cancelled」を示す新しい構文と、同期と非同期の両方でキャンセルを処理するための新しい構文があります。 コード:

すべきでないこと

実際のコードではなく、提案が取り消されました

    try {
      // Start spinner, then:
      await someAction();
    }
    catch cancel (reason) {
      // Maybe do nothing?
    }
    catch (err) {
      // Show error message
    }
    finally {
      // Stop spinner
    }

アクションがキャンセルされたときの最も一般的なアクションは、何もしないことです。上記の提案は分離され、 abort エラーを特に処理する必要はありません。catch cancel による許可 キャンセルされたアクションが通知されますが、ほとんどの場合、その必要はありません。

これは TC39 のステージ 1 に至りましたが、合意に至らず、提案が撤回されました

別の提案である AbortController では新しい構文が不要なため、意味をなさらなかった TC39 で指定してくださいJavaScript から必要なものはすべて用意されているため、 インターフェース(特に DOM 標準)について説明します。その決定後に 残りは比較的すぐにまとまっていました。

仕様の大幅な変更

XMLHttpRequest は何年も前に中止されていましたが、仕様はかなりあいまいなものでした。はっきりと分かりませんでした。 基になるネットワーク アクティビティを回避 / 終了できます。 abort() が呼び出されてから取得完了までの間に競合状態がありました。

今回は正しく機能させようとしましたが、仕様が大きく変更され、 確認しています(私のミスです。Anne van KesterenDomenic Denicola に引っかかった経験あり)と、まともなテストでした。

でも、私たちは今ここにいるのです!非同期アクションを中止するための新しいウェブ プリミティブが追加されました。複数のフェッチは 制御できるということです。この後の方で、取得のライフサイクル全体を通して優先度の変更を有効にする方法を見ていきます。 取得の進行状況を監視するための API。