Open cheungseol opened 5 years ago
AbortController 是一个DOM API,提供了终止 Promise 网络请求继续执行的能力
演示一个根据用户输入内容调用接口请求数据的🌰例子
调用 AbortController 构造函数,返回 controller 实例对象 和 signal 对象,将 signal 作为参数传给 fetch ,执行 controller.abort() 方法终止 fetch 请求
// 调用 AbortController 构造函数,新建一个实例对象 let abortController = new AbortController(); let { signal } = abortController; // 将实例对象返回的 signal 作为参数传给 fetch 请求 async function getGists(){ const response = await fetch('https://api.github.com/gists', { signal }); const gists = await response.json(); console.info(gists); }; // 可以通过绑定 EventListener 监听 signal 变化 signal.addEventListener('abort', () => { console.error('[SIGNAL ABORT EVENT TRIGGERED]') }) signal.onabort = () => { console.error('[SIGNAL ONABORT EVENT TRIGGERED]') } getGists() // 触发实例对象的 abort 方法,signal 触发一个 AbortDomError 事件 abortController.abort() // signal 的状态变为 aborted,未执行完的 fetch 被 abort 终止,同时触发 EventListener 队列中的回调
const racefetch = async function(timeout = 3000){ let fetchPromise = fetch('https://api.github.com/gists') .then(r=> r.json()) .then(p => { console.log('[RESP]', p) return p }) let timeoutPromise = new Promise(function(resolve, reject){ setTimeout(()=>{ reject(new Error("fetch timeout")) }, timeout) }); return Promise.race([fetchPromise, timeoutPromise]) }
可以看到👇下面的测试结果,虽然能实现超时 throw error 的效果,但并没有真正阻止 fetch 请求的传输 🤷♂️
const controllerFetch = async function (timeout = 3000) { async function doRequest() { const controller = new AbortController(); const signal = controller.signal; const timer = setTimeout(() => controller.abort(), timeout); try { const resp = await fetch('https://api.github.com/gists', {signal}) const respJson = await resp.json() return respJson } catch (e) { return Promise.reject(e); } finally { clearTimeout(timer); } } return doRequest(); }
下图是使用使用 AbortController 的方式,可以在设定的 timeout 时真正意义上的关闭网络连接 🌟,0 字节传输
看源码的实现就可以知道为什么,AbortSignal 在 abort 之后,状态就变成了 aborted,那么下次调用 fetch 请求的时候,当读到 AbortSignal 的状态是 aborted 的时候,就直接 reject 返回了。
// Return early if already aborted, thus avoiding making an HTTP request if (signal.aborted) { return Promise.reject(abortError); }
所以如果有请求重试的需求的话,需要每次调用 fetch 前都重新 new 一个 AbortController,返回一个新的 signal
// 第一个 AbortController let firstController = new AbortController(); let { signal: firstSignal } = firstController; // 第二个 AbortController let anotherController = new AbortController(); let { signal: anotherSignal } = anotherController; async function getGists(signal){ const response = await fetch('https://api.github.com/gists', { signal }); const gists = await response.json(); console.log(gists); }; // 终止第一个 AbortSignal firstController.abort() // 被 firstSignal 标记的请求已经终止 getGists(firstSignal) // anotherSignal 标记的 fetch 请求不受影响仍然可以执行 getGists(anotherSignal)
const controller = new AbortController() const { signal } = controller async function fetchAll({ signal }={}) { const fetchSingle = [1,2,3].map(async () => { console.info("[SIGANL STATUS]", signal) const response = await fetch('https://api.github.com/gists', { signal }); return response.json(); }); return Promise.all(fetchSingle); } // 调用 const controller = new AbortController(); const signal = controller.signal; fetchAll({ signal })
👇 下图可以看到,当 signal 被 abort 后, 所有被 signal 标记的 fetch 请求都终止了:
🥇Edge 浏览器率先在 2017 年支持 AbortController API ,FireFox 紧随其后 🏃♂️,我们常用的 Chrome 是在 2018 年的 66 版本开始支持的 😅
需要浏览器的支持去关闭 TCP socket 连接才能真正意义上结束一次网络请求。这里介绍 fetch polyfill 对 AbortController 的实现。
虽然是一个 DOM API,但是借助 polyfill 可以实现在 service-worker、nodejs 等环境中使用AbortController(相关 MR)
fetch polyfill 在 service-worker 中的实现其实是引用了abortcontroller-polyfill 模块,模块主要完成两件事情:
通过源码分析,很容易发现其实这个 polyfill 的目的是让用户可以在不支持 AbortController 的 js 执行环境中仍然能够使用这个属性,并且得到相似的运行结果。虽然结果看上去相似,但效果并不相同 🙅♂️(它并无法真正的去实现关闭 TCP 连接,只是在 abort 事件发生时不再对 fetch请求结果作处理)
业务中可能遇到的场景
Demo
演示一个根据用户输入内容调用接口请求数据的🌰例子
使用方式
调用 AbortController 构造函数,返回 controller 实例对象 和 signal 对象,将 signal 作为参数传给 fetch ,执行 controller.abort() 方法终止 fetch 请求
请求超时
可以看到👇下面的测试结果,虽然能实现超时 throw error 的效果,但并没有真正阻止 fetch 请求的传输 🤷♂️
下图是使用使用 AbortController 的方式,可以在设定的 timeout 时真正意义上的关闭网络连接 🌟,0 字节传输
使用中需要注意的地方
看源码的实现就可以知道为什么,AbortSignal 在 abort 之后,状态就变成了 aborted,那么下次调用 fetch 请求的时候,当读到 AbortSignal 的状态是 aborted 的时候,就直接 reject 返回了。
所以如果有请求重试的需求的话,需要每次调用 fetch 前都重新 new 一个 AbortController,返回一个新的 signal
👇 下图可以看到,当 signal 被 abort 后, 所有被 signal 标记的 fetch 请求都终止了:
兼容性 🌚
🥇Edge 浏览器率先在 2017 年支持 AbortController API ,FireFox 紧随其后 🏃♂️,我们常用的 Chrome 是在 2018 年的 66 版本开始支持的 😅
AbortController 的实现
需要浏览器的支持去关闭 TCP socket 连接才能真正意义上结束一次网络请求。这里介绍 fetch polyfill 对 AbortController 的实现。
fetch polyfill 对 AbortController 的实现
虽然是一个 DOM API,但是借助 polyfill 可以实现在 service-worker、nodejs 等环境中使用AbortController(相关 MR)
abortcontroller-polyfill 模块
fetch polyfill 在 service-worker 中的实现其实是引用了abortcontroller-polyfill 模块,模块主要完成两件事情:
通过源码分析,很容易发现其实这个 polyfill 的目的是让用户可以在不支持 AbortController 的 js 执行环境中仍然能够使用这个属性,并且得到相似的运行结果。虽然结果看上去相似,但效果并不相同 🙅♂️(它并无法真正的去实现关闭 TCP 连接,只是在 abort 事件发生时不再对 fetch请求结果作处理)
参考