共计 2953 个字符,预计需要花费 8 分钟才能阅读完成。
绝大部分状况,网络申请都是先申请先响应。然而某些状况下,因为未知的一些问题,可能会导致先申请的 api 后返回。最简略的解决方案就是增加 loading 状态,在所有申请都实现后能力进行下一次申请。
但不是所有的业务都能够采纳这种形式。这时候开发者就须要对其进行解决以防止渲染谬误数据。
应用“版本号”
咱们能够应用版本号来决策业务解决以及数据渲染:
const invariant = (condition: boolean, errorMsg: string) => {if (condition) {throw new Error(errorMsg) | |
} | |
} | |
let versionForXXXQuery = 0; | |
const checkVersionForXXXQuery = (currentVersion: number) => { | |
// 版本不匹配,就抛出谬误 | |
invariant(currentVersion !== versionForXXXQuery, 'The current version is wrong') | |
} | |
const XXXQuery = async () => { | |
// 此处只能应用 ++versionForXXXQuery 而不能应用 versionForXXXQuery++ | |
// 否则版本永远不对应 | |
const queryVersion = ++versionForXXXQuery; | |
// 业务申请 | |
checkVersion(queryVersion) | |
// 业务解决 | |
//?界面渲染 | |
// 业务申请 | |
checkVersion(queryVersion) | |
// 业务解决 | |
//?界面渲染 | |
} |
如此,先申请的 api 后返回就会被谬误中止执行,但最终渲染到界面上的只有最新版本的申请。然而该计划对业务的侵入性太强。尽管咱们能够利用 class 和 AOP 来简代码和逻辑。但对于开发来说仍旧不敌对。这时候咱们能够应用 AbortController。
应用 AbortController
AbortController 勾销之前申请
话不多说,先应用 AbortController 实现下面雷同的性能。
let abortControllerForXXXQuery: AbortController | null = null | |
const XXXQuery = async () => { | |
// 以后有停止控制器, 间接把上一次勾销 | |
if (abortControllerForXXXQuery) {abortControllerForXXXQuery.abort() | |
} | |
// 新建控制器 | |
abortControllerForXXXQuery = new AbortController(); | |
// 获取信号 | |
const {signal} = abortControllerForXXXQuery | |
const resA = await fetch('xxxA', { signal}); | |
// 业务解决 | |
//?界面渲染 | |
const resB = await fetch('xxxB', { signal}); | |
// 业务解决 | |
//?界面渲染 | |
} |
咱们能够看到:代码非常简单,同时失去了性能加强,浏览器将提前进行获取数据(注:服务器依旧会解决屡次申请,只能通过 loading 来升高服务器压力)。
AbortController 移除绑定事件
尽管代码很简略,然而为什么须要这样增加一个 AbortController 类而不是间接通过增加 api 来进行停止网络申请操作呢?这样不是减少了复杂度吗?笔者开始也是这样认为的。到前面才发现。AbortController 类尽管较为简单了,然而它是通用的,因而 AbortController 能够被其余 Web 规范和 JavaScript 库应用。
const controller = new AbortController() | |
const {signal} = controller | |
// 增加事件并传递 signal | |
window.addEventListener('click', () => {console.log('can abort') | |
}, {signal}) | |
window.addEventListener('click', () => {console.log('click') | |
}); | |
// 开始申请并且增加 signal | |
fetch('xxxA', { signal}) | |
// 移除第一个 click 事件同时停止未实现的申请 | |
controller.abort() |
通用的 AbortController
既然它是通用的,那是不是也能够终止业务办法呢。答案是必定的。先来看看 AbortController 到底为啥可能通用呢?
AbortController 提供了一个信号量 signal 和停止 abort 办法, 通过这个信号量能够获取状态以及绑定事件。
const controller = new AbortController(); | |
// 获取信号量 | |
const {signal} = controller; | |
// 获取以后是否曾经执行过 abort,目前返回 false | |
signal.aborted | |
// 增加事件 | |
signal.addEventListener('abort', () => {console.log('触发 abort') | |
}) | |
// 增加事件 | |
signal.addEventListener('abort', () => {console.log('触发 abort2') | |
}) | |
// 停止 (不能够解构间接执行 abort,有 this 指向问题) | |
// 控制台打印 触发 abort,触发 abort2 | |
controller.abort() | |
// 以后是否曾经执行过 abort, 返回 ture | |
signal.aborted | |
// 控制台无反馈 | |
controller.abort(); |
无疑,上述的事件增加了 abort 事件的监听。综上,笔者简略封装了一下 AbortController。Helper 类如下所示:
class AbortControllerHelper { | |
private readonly signal: AbortSignal | |
constructor(signal: AbortSignal) { | |
this.signal = signal | |
signal.addEventListener('abort', () => this.abort()) | |
} | |
/** | |
* 执行调用办法,只须要 signal 状态的话则无需在子类实现 | |
*/ | |
abort = (): void => {} | |
/** | |
* 查看以后是否能够执行 | |
* @param useBoolean 是否应用布尔值返回 | |
* @returns | |
*/ | |
checkCanExecution = (useBoolean: boolean = false): boolean => {const { aborted} = this.signal | |
// 如果应用布尔值,返回是否能够继续执行 | |
if (useBoolean) {return !aborted} | |
// 间接抛出异样 | |
if (aborted) {throw new Error('abort has already triggered'); | |
} | |
return true | |
} | |
} |
如此,开发者能够增加子类继承 AbortControllerHelper 并放入 signal。而后通过一个 AbortController 停止多个乃至多种不同事件。
激励一下
如果你感觉这篇文章不错,心愿能够给与我一些激励,在我的 github 博客下帮忙 star 一下。
博客地址
参考资料
AbortController MDN