一般来说,大家可能不会注意到在切换页面时的请求取消问题,一是网速很快,基本几十甚至几毫秒就可以得到返回结果,二是就算比较慢但最终还是会得到结果不会报错,但是你如果用户在弱网环境下可能就会出现用户已经从 A 页面切换到了 B 页面,但是提示我获取 A 页面的信息失败。(极端的全局加 Loading Layer 的不在讨论范围内 (^^ゞ)
先下一个假的 Server,以便测试:
r.GET("/user/:id", func(c *gin.Context) {
time.Sleep(time.Second * 5)
c.JSON(200, map[string]string{"name": "longhe"})
})
比如,有一单页应用,鼠标移动到用户头像上会显示一个悬浮层,悬浮层显示该用户信息。
一般情况下,会在 useEffect 里加载用户信息,但如果很快鼠标移出用户头像,那么此时还在请求数据,如下伪代码:
const loadData = (id) => {
const { data } = await getUser(id)
if (data) {
console.log(data)
}
}
useEffect(() => {
loadData(props.id)
}, [])
AbortController 闪亮登场,我们在 useEffect 的副作用函数中去 Abort 这个请求,这样在组件被销毁时将自动中断请求。
const loadData = (id, controller) => {
const { data } = await getUser(id, { signal: controller.signal })
if (data) {
console.log(data)
}
}
useEffect(async () => {
let controller = new AbortController()
loadData(props.id, controller)
return () => {
controller.abort()
}
}, [])
上面代码中的 signal 要一路传下去,比如一般会有一个 services/user.js 文件,然后还有一个封装的 request.js 文件。
// user.js
import { request } from "./request";
export async function getUser(id, options) {
return request(`user/${id}`, options);
}
// request.js
export async function request(url, userOptions) {
let options = {
method: 'GET',
...userOptions
}
return fetch(url, options).
then(res => res.json()).
......
}
另外,需要注意的是 AbortController 是支持同时挺多个请求的,全部使用同一个 controller 就行了,但是一旦 abort 之后,就不能附带这个 controller 去发送请求了,后续新的请求会直接 abort。
如果是某些特殊接口,比如文件上传或者下载之类的可能已经满足需求了,但是如果是所有页面要求切换路由时都停止呢?可以尝试监听路由变化,如果存在非目标页面的请求全部中断掉。
useEffect(() => {
Object.entries(window.requestMap).forEach(([key, controller]) => {
if (controller && key !== location.pathname) {
controller.abort()
delete window.requestMap[key]
}
})
}, [location])
if (!window.requestMap[location.pathname] || window.requestMap[location.pathname].signal.aborted === true) {
window.requestMap[location.pathname] = new AbortController()
}
这样做势会导致一些共享数据的获取被取消,这个需要衡量处置,比如必要信息可以加 loading 阻止用户动作。另外对于集成度比较高的具有很多组件组合而成的页面,且并没有使用路由这种具有追踪机制的工具,可能就需要使用第一种单独处理或者在操控组件显隐时发送事件去注册和处理。