pfan123 / Articles

经验文章
169 stars 25 forks source link

Fetch 实现 Abort #68

Open pfan123 opened 4 years ago

pfan123 commented 4 years ago

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

fetch(String url, [, Object options]).then(function(response) { ... }).catch(err => {})

目前,浏览器端原生获取数据,发请求的主要是 XMLHttpRequestfetchXMLHttpRequest 作为元老级的存在, fetch 是在 ES6 中推出的更现代的 API。XMLHttpRequest 本身是支持请求中断的 abort , 而 fetch 不支持 abort,不过目前可以通过 AbortController 实现。

fetch 设计之初是不支持中断的,开发者 2015 年在 GitHub 上提的 issue ,目前解决方案还在讨论阶段,其中就包括 cancelable-promises 及其他 hack 方法。

const c = new FetchController
fetch(url, {controller: c})
c.abort()
// 或
fetch(url, {controller: c => c.abort()})

但是,现在有了通用的 AbortControllerAbortSignal API,这些 API 是由 DOM 标准规范 提供的,而不是由语言本身提供的。

什么是 AbortController?

AbortController 接口代表一个控制器对象,允许在需要时中止一个或多个 DOM 请求。

使用 AbortController.AbortController() 构造函数创建一个新的 AbortController 对象。 使用 AbortSignal 对象完成与DOM请求的通信。

// 构造一个新的 AbortController 对象实例
AbortController.AbortController()  // 目前大部分浏览器不支持此方法构造

const controller = new AbortController()

// 返回一个AbortSignal对象实例,它可以用来 with/abort 一个DOM请求
AbortController.signal 

// 中止一个尚未完成的DOM请求, 能够中止fetch 请求,任何响应Body的消费者和流
AbortController.abort()

以下是 AbortController 的基本用法

// 创建 AbortController 的实例
const controller = new AbortController()
const signal = controller.signal

// 监听 abort 事件,在 controller.abort() 执行后执行回调打印 
signal.addEventListener('abort', () => {
    console.log(signal.aborted) // true
})

// 触发中断
controller.abort()

浏览器支持情况

查询支持情况 AbortController,整体支持性不是特别好, 可以使用 abort-controller/polyfilabort-controller控件

fetch_abortcontroller

使用 AbortController 中断 fetch 请求

fetch 接受 AbortSignal 作为第二个参数的一部分。

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

fetch('http://jd.com', { signal })
    .then(r => r.json())
    .then(response => console.log(response))
    .catch(err => {
        if (err.name === 'AbortError') {
            console.log('Fetch was aborted')
        } else {
            console.log('Error', err)
        }
    })

// 在 2s 后中断请求,将触发 'AbortError'
setTimeout(() => controller.abort(), 2000)

触发 controller.abort() 会中断 fetch 的 request 和 response。AbortController 不仅适用于 fetch,可以适用到中止任意异步事件。

思考

客户端中断请求反馈失败提示,但服务端会出现真实处理,状态变成成功了,导致客户端刷新页面状态变更了,那这种不一致一般使用优化策略 幂等机制

Other Resources