如何中断一个正在发出的请求

9,638 阅读3分钟

在用户交互或者最新输入的时候,你常常需要在一个 web 应用中频繁发起请求。比如说在输入文字的时候的自动完成操作或者是在地图上放大缩小的操作。让我们花一点时间去思考一下这些例子。首先是自动完成,每一次我们输入的时候(或者更少在用 debounce 时)我们就会发出一个请求。如果用户输入改变的时候,旧的请求就会与现在无关,当我们持续打字输入的时候(比如 “java” 和 “javascript” )。也许有很多冗余请求,在我们拿到我们需要的东西的时候。

然后是地图的例子。我们在缩放查看地图的时候,我们对于之前缩放级别的图块不再感兴趣。与此同时,为了冗余数据,许多请求还处于等待。

带着我们第一个例子,看看实现一个自动完成场景的原生代码。为了本文的目的,我们将使用更现代的 fetch 而不是 XMLHttpRequest 来提出网络请求。 代码如下:

autocompleteInput.addEventListener('keydown', function() {

    const url = "https://api.example.com/autocomplete"

    fetch(url)
        .then((response) => {
            // Do something with the response
            updateAutocompleteMenu()
        })
        .catch((error) => {
            // Something went wrong
            handleAutocompleteError(error);
        })

});

这个例子的问题就是每一个请求都要完成,即使它不再相关。我们可以实现一个额外的逻辑在 updateAutocompeleteMenu 中去防止额外的代码,但是却不能真正停止这个请求。值得注意的是,浏览器有同时请求的限制,这意味着一旦限制被触发时,请求将队列化(而且每个浏览器限制又不同)。

Abortable Fetch

我们可以用来解决上述问题的新浏览器技术是 Abortable Fetch 。 Abortable Fetch 依赖浏览器的规范 AbortController 。这个控制器有 signal 属性,我们用它传递,在之后的时机中用控制器中断方法去取消请求。

一个例子如下:

autocompleteInput.addEventListener('keydown', function() {
const url = "https://api.example.com/autocomplete"
let controller;
let signal;

autocompleteInput.addEventListener('keyup', () => {

    if (controller !== undefined) {
        // Cancel the previous request
        controller.abort();
    }

    // Feature detect
    if ("AbortController" in window) {
        controller = new AbortController;
        signal = controller.signal;
    }

    // Pass the signal to the fetch request
    fetch(url, {signal})
        .then((response) => {
            // Do something with the response
            updateAutocompleteMenu()
        })
        .catch((error) => {
            // Something went wrong
            handleAutocompleteError(error);
        })
    });

});

我们进行特性检测,以确定我们是否可以使用 AbortController (它在 Edge,Firefox,Opera 和 Chrome 66 中支持!)。我们确认是否有一个控制器已经创建,如果是,我们调用 controller.abort() 去取消之前的请求。你也可以一次用相同的 signal 取消多个 fetches 。

一个小例子

我创建了一个展示如何使用 Abortable Fetch 小例子,基于自动完成场景的想法(没有任何实现细节!)。去理解每当输入网络请求时会发生什么。 如果您在旧请求完成之前进行了新的输入,它将中止先前的抓取。 在实践中它看起来有点像这样:

abortable fetch

源代码

深入思考

也许 AbortController 最酷的部分是它被设计成一个中止异步任务的通用机制。它是 WHATWG specification 的一部分。这意味着它是DOM规范而不是语言(ECMAScript)规范,但对于前端开发来说,这仍然是一个有用的功能。你可以利用它作为一个更清晰的异步控制流机制,用于实现异步任务的时候(即当使用 Promises 时)。这篇Bram Van Damme super article for a more detailed example 文章会有更多我所讲述的细节。

本翻译自原文 - Cancelling Requests with Abortable Fetch。不定期更新技术博文欢迎star。由于本人水平有限,错误之处在所难免,敬请指正!转载请注明出处,保留原文链接以及作者信息。