写点什么

中断操作:AbortController 学习笔记

作者:zhoulujun
  • 2022 年 6 月 25 日
  • 本文字数:2974 字

    阅读完需:约 10 分钟

前端面试一般喜欢问:


请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么?


如何中断请求 fetch 的原理分析和应用?


在看来《使用 AbortController 终止 fetch 请求》,觉得写的非常详细,于是提炼下笔记:


AbortController 背景介绍在现在的浏览器中,有两种主要的方法发送请求:XMLHttpRequest 和 fetch。XMLHttpRequest 这个接口在浏览器中存在很长一段时间了,fetch 则是 ES2015 引入的特性。


XMLHttpRequest 可以在请求中途终止(abortable)。举个例子


let xhr = new XMLHttpRequest();xhr.method = 'GET';xhr.url = 'https://slowmo.glitch.me/5000';xhr.open(method, url, true);xhr.send();


// Abort the request at a later stageabortButton.addEventListener('click', function() {xhr.abort();});fetch 刚开始引入时并不支持终止请求。Github 上最早 在 2015 年就有终止 fetch 请求的提案 issue 出现。在 fetch 规范之外也有许多解决这个问题的方案,像 cancelable-promises 和其他 hacks。


终于,通用的 AbortController 和 AbortSignal API 出来了。该 API 在 DOM 标准 中定义,而不是在语言规范中定义的。


什么是 AbortControllerAbortController 是一个 DOM API。MDN 上对它的介绍是 AbortController 接口表示一个控制器对象,允许根据需要终止一个或多个 Web 请求。


AbortController 可以用在 fetch 和 addEventListener,分别用来废弃请求和废弃监听器。


具体看官网:


https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController/AbortController


https://caniuse.com/?search=AbortController


DOM 文档 中有这么一段话:


虽然 Promise 没有提供内置的终止算法(aborting mechanism),但是许多使用它们的 API 需要终止语义。AbortController 提供一个 abort() 方法来支持这些需求,这个方法用来切换相应 AbortSignal 对象的状态。希望支持终止功能的 API 可以接受 AbortSignal 对象,并基于其状态来确定执行流程。


AbortController 由两部分构成:abort-signal 和 abort-controller,架构图如下:


红色部分是我们重点需要关注的部分,因为它们将直接体现在实际应用中


AbortSignal 源码解读


AbortSignal 源码解析...export default class AbortSignal extends EventTarget<Events, EventAttributes> {/*** 从 abortedFlags 中获取当前 AbortSignal 实例 aborted 状态*/public get aborted(): boolean {const aborted = abortedFlags.get(this)if (typeof aborted !== "boolean") {throw new TypeError(Expected 'this' to be an 'AbortSignal' object, but got ${ this === null ? "null" : typeof this },)}return aborted}}// 设置 abort 自定义事件 defineEventAttribute(AbortSignal.prototype, "abort")


...


/**


  • 创建一个 AbortSinal 实例,并设置 aborted 状态为 false,存入 abortedFlags 中,同时绑定 abort 事件属性*/export function createAbortSignal(): AbortSignal {const signal = Object.create(AbortSignal.prototype)EventTarget.call(signal)abortedFlags.set(signal, false)return signal}


/**


  • 设置 AbortSinal 实例 aborted 状态为 true,同时触发 abort 监听事件回调*/export function abortSignal(signal: AbortSignal): void {if (abortedFlags.get(signal) !== false) {return}

  • abortedFlags.set(signal, true)signal.dispatchEvent<"abort">({ type: "abort" })}...AbortSignal 继承 EventTarget(第三方依赖包,作用是赋予实例监听自定义事件,它会帮你解决兼容性问题 addEventListener/onXX/dispatchEvent),AbortSignal 自定义了 abort 监听事件


AbortSignal.aborted():获取当前实例是否已经启动了 abort 监听事件。


abortedFlags:map 类型,用于存储每个实例的是否已经启动了 abort 监听事件,默认为 false(createAbortSignal 创建实例的时候设置),调用 abortSignal 函数的时候会设置为 true


createAbortSignal():构建函数,初始化实例对象为 false,绑定 abort 监听事件(需要用户自己设置 abort 监听回调事件)


abortSignal(instance):设置当前实例状态为 ture,同时触发 abort 监听回调事件


AbortController 源码解析...export default class AbortController {/*** 构造函数,创建一个 AbortSignal 实例并存入 signals 中*/public constructor() {signals.set(this, createAbortSignal())}


/** * 从signals中获取当前AbortSignal实例 */public get signal(): AbortSignal {    return getSignal(this)}
/** * 先从signals中获取当前AbortSignal实例,然后设置实例aborted状态为true,触发abort监听回调事件 */public abort(): void { abortSignal(getSignal(this))}
复制代码


}...AbortController 构造函数中会调用 createAbortSignal 创建 AbortSignal 实例并存入一个 Map 类型的 signals 中。


abort()方法会调用 abortSignal 函数,传入的参数就是从 signals 中取出来的 AbortSignal 实例


signal()方法作用是从 signals 中取出 AbortSignal 实例


参考源码:EventTarget 源码传送门


AbortController 源码传送门


fetch 源码传送门


AbortController addEventListener 众所周知,如果需要 removeEventListenr(type, callback), 它的 callback 必须和 addEventListener 是同一个函数引用,而在某些业务场景下,我们并不想多写函数可以改成用 signal 来控制。


例如,当在按钮鼠标时设置一个监听器,在监听器中再监听鼠标移动,鼠标松开关闭监听器:


document.addEventListener('mousedown', callback);document.addEventListener('mouseup', callback2);function callback (e) {document.addEventListener('mousemove', callback3);}


function callback2 (e) {document.removeEventListener('mousemove', callback3);}


function callback3(event) {}如果改写成用 AbortController 怎么写呢?


const controller = new AbortController();function callback (e) {  document.addEventListener('mousemove',  (e) => {        },{       signal: controller.signal    });
复制代码


}document.addEventListener('mousedown', callback);document.addEventListener('mouseup', controller.abort);


高级进阶每次请求,都会重新创建一个 AbortSignal 实例吗?


答:是的


signals 和 abortedFlags 都是 Map 类型,每一个请求都会创建一个实例,随着时间的推移和请求的增多,如何防止缓存雪崩问题?


答:signals 和 abortedFlags 准确的说是 WeakMap 类型,而 WeakMap 跟 Map 会有所区别,WeakMap 的键只能是对象的引用,当垃圾回收机制执行时,会检测 WeakMap 的键是否被引用,若没有被引用,该键对会被删除,并自动回收,从而防止缓存雪崩的问题。


AbortSignal 是如何具备监听事件能力的?


答:它本身并不具备事件处理能力,它继承了一个 EventTarget 类使其具备监听处理事件能力


参考文章:


一个可中断请求 fetch 的原理分析和应用 https://zhuanlan.zhihu.com/p/416572062


[译] 使用 AbortController 终止 fetch 请求 https://juejin.cn/post/6844904072051425293


一个可中断请求 fetch 的原理分析和应用(之前的笔记) https://github.com/ctq123/blogs/issues/9


AbortController 使用场景探索 https://www.jianshu.com/p/2f23c33e1922


转载本站文章《中断操作:AbortController 学习笔记》,请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2022_0530_8824.html

发布于: 刚刚阅读数: 3
用户头像

zhoulujun

关注

还未添加个人签名 2021.06.25 加入

还未添加个人简介

评论

发布
暂无评论
中断操作:AbortController学习笔记_zhoulujun_InfoQ写作社区