技術共有

フロントエンドでインターフェイス呼び出しをキャンセルする方法

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

🧑‍💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣

1. xmlHttpRequest はどのようにリクエストをキャンセルしますか?

インスタンス化された XMLHttpRequest オブジェクトには中止メソッドもあります。

const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(e) {
  console.log(this.responseText);
});
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.send();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// 返回
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

送信直後に中止された場合はキャンセルします

// xhr的取消操作:执行过程比较模糊,不知道abort什么时机进行处理
xhr.abort()

  • 1
  • 2
  • 3

タイマー中にリクエストをキャンセルした場合 (タイマーの継続時間がインターフェイスのリクエストの継続時間と同じである場合)、リソースは取得されたことがわかりますが、コンソールには何も表示されません。
ここに画像の説明を挿入します

2. コントローラーの中止

const ac = new AbortController();
const { signal } = ac;
const url = "https://jsonplaceholder.typicode.com/todos/1";
​
fetch(url, { signal })
  .then((res) => res.json())
  .then((json) => console.log(json));

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

abort を直接使用してリクエストをキャンセルします

ac.abort()

  • 1
  • 2

ここに画像の説明を挿入します
ここで報告されるエラーの理由は、エラーがキャプチャされなかったことです。

// 修改后的代码
fetch(url, { signal: ac.signal })
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch(e => console.log(e)) // DOMException: signal is aborted without reason
ac.abort() // abort接受一个入参,会被传递到signal的reason属性中

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

なぜこのようにキャンセルできるのでしょうか?

fetch はシグナル オブジェクトのステータスを監視し、リクエストを終了できます。

2.1 複数のリクエストを同時にキャンセルするにはどうすればよいですか?

const ac = new AbortController();
const { signal } = ac;
const url = "https://jsonplaceholder.typicode.com/todos";
​
const todoRequest = (id, { signal }) => {
  fetch(`${url}/${id}`, { signal })
    .then((res) => res.json())
    .then((json) => console.log(json))
    .catch((e) => console.log(e)); // DOMException: signal is aborted without reason
};
​
todoRequest(1, { signal });
todoRequest(2, { signal });
todoRequest(3, { signal });
​
ac.abort("cancled");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

ここに画像の説明を挿入します

2.2 中止シグナル

シグナル オブジェクトを表すために使用されるインターフェイスです。これにより、実行中の非同期操作と通信できるようになり、操作が完了する前に中止できます。

2.3 AbortSignal メソッド

2.3.1 中止

中止された AbortSignal オブジェクトを作成するために使用される静的メソッド。このメソッドを呼び出すと、中断ステータスが true の AbortSignal インスタンスが返されます。

const signalAbout = AbortSignal.abort(); // AbortSignal {aborted: true, reason: DOMException: signal is aborted without reason...}

  • 1
  • 2

2.3.2 throwIfAborted メソッド

コードを実行する前に、AbortSignal が中止されたかどうかを確認するために使用されます。 AbortSignal が中止された場合は、AbortError がスローされます。このメソッドは、開発者が特定の操作を実行する前に操作が中止されないようにし、不必要な処理を回避するのに役立ちます。

const signalAbout = AbortSignal.abort('abortedReason');
try {
    signalAbout.throwIfAborted(); // 抛出error: abortedReason
} catch (error) {
    console.log(error);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.3.3 タイムアウト

指定された時間が経過すると自動的に終了する AbortSignal オブジェクトを作成するために使用されます。これは、リクエストのタイムアウトを設定する必要がある場合に便利です。

// 使用 AbortSignal.timeout 设置 10ms超时
const signalAbout = AbortSignal.timeout(10);
const todoRequest = (id, { signal }) => {
  fetch(`${url}/${id}`, { signal })
    .then((res) => res.json())
    .then((json) => console.log("json: ", json))
    .catch((e) => console.log("err: ", e)); //DOMException: signal timed out 
};
todoRequest(1, { signal: signalAbout });

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

ここに画像の説明を挿入します

2.3.3.1 イベントリスナーを追加 => 未終了から終了へ

AbortSignal は、中止イベントをリッスンするために使用され、EventTarget はイベント リスナーを追加、削除、トリガーするメカニズムを提供するため、AbortSignal は EventTarget から継承します。

const signalAbout = AbortSignal.timeout(10);
signalAbout.addEventListener("abort", (e) => {
    console.log("aborted: ", e);
})
​

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

e の出力は次のようになります。
ここに画像の説明を挿入します

3. プロアクティブにキャンセルされた約束を実装する

const ac = new AbortController();
const { signal } = ac;
​
const cancelablePromise = ({signal}) => 
    new Promise((resolve, reject) => {
        // 情况1:直接主动取消
        signal?.throwIfAborted(); // 也可以用reject
​
        // 情况2:正常处理业务逻辑
​
        setTimeout(() => {
            Math.random() > 0.5 ? resolve('data') : reject('fetch error');
        }, 1000);
​
        // 情况3:超时 todo?
​
        // 监听取消
        signal.addEventListener("abort", () => {
            reject(signal?.reason);
        });
    })
// 发起网络请求
cancelablePromise({signal})
.then(res => console.log('res: ', res))
.catch(err => console.log('err: ', err))
// 情况1 
// ac.abort('用户离开页面了') // err:  用户离开页面了
​
// 情况2 正常请求 err:  fetch error || res:  data

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

4. シグナルを使用してイベントのリスニングをキャンセルするにはどうすればよいですか?

複数のイベント リスナーを要素に追加する場合、removeEventListener のように各イベントをキャンセルする必要はなく、そのたびに対応するイベントのイベント ハンドルを書き込む必要があります。

シグナルを使用する場合、シグナルを一度キャンセルするだけで、すべてのイベント監視がキャンセルされます。

const ac = new AbortController();
const { signal } = ac;
const eleA = document.querySelector('#a');
const eleB = document.querySelector('#b');
​
function aEleHandler () {}; // 事件
eleA.addEventListener('click', aEleHandler, {signal}); // 无论绑定多少个事件,都只需要一个signal
​
eleB.addEventListener('click', () => {
    ac.abort(); // 只需要取消一次
})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

5. データアセンブリのために複数のインターフェイスが要求されるシナリオ

ネットワーク速度が低い場合に、この継続的なネットワーク要求をキャンセルするにはどうすればよいですか?

const ac = new AbortController();
const { signal } = ac;
const fetchAndRenderAction = (signal) => {
    requestData(signal); // 多个串行或者并行的接口
    drawAndRender(signal); // 异步渲染
}
​
try{
    fetchAndRenderAction({signal})
}catch{
    // dosomething...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

6. まとめ

ユーザーが積極的にページから離れた場合、またはユーザーのネットワークが非常にスタックしている場合 (予想される戻り順序は次のとおりです: インターフェイス 1 => インターフェイス 2; しかし、インターフェイス 1 の戻りが遅すぎるため、順序が混乱します)。これには手動が必要です。リクエストの終了。コンストラクター AbortController のインスタンス セマフォ信号 (ref として保存可能)、信号はフェッチのパラメーターとして使用され、各リクエストで中止メソッドを手動で呼び出して前のリクエストをキャンセルできます。

お役に立てば、ぜひ注目していただければと思います。これにより、皆さんが一緒に議論し、学び、進歩できるようになります。
ここに画像の説明を挿入します