看到B站上一个视频,如何避免相同请求。页面上一些没有副作用且不会改变返回结果的请求是重复的,应该只请求一次,然后所有请求返回相同的结果。
视频里的做法是类似于发布订阅模式,数组记录回调函数,在异步函数执行完的时候,调用所有回调函数,然后缓存值。这种做法让我想起之前几天看到的 webpack 动态 import 文件的处理方式,创建一个 Promise ,记录 resolve,reject 函数,等到 jsonp 获取到数据再根据路径从 map 中调用 resolve 函数,继续执行。(不过这种做法很常见)
我和视频中的思路大体相同,但是实现方式不太一样,想验证一下自己的想法能否实现遂作如下代码。
我的思路就是只针对当前的请求,根据当前请求可能遇到的问题进行处理。 (可以看作我们根据闭包缓存了一些信息,视频缓存了 callabck 函数,而我缓存了请求的 promise )
const asyncOnce = (fn: () => Promise<unknown>, errorHandler: (err: Error) => void) => {
// 如果参数有可能会变化,可以将下面三项变成
// record<string,{loading:boolean,pendingPromise:Promise<unknown>|null,result:unknown|null}>
let loading = false
let pendingPromise: Promise<unknown> | null = null
let result: unknown | null = null
return async () => {
if (!loading && result) {
// 有缓存
return result
}
else if (loading) {
// 没缓存,但是在请求
await pendingPromise?.catch(errorHandler)
return result
} else {
// 没缓存,没请求,直接发起请求
return pendingPromise = new Promise((resolve, reject) => {
loading = true
fn().then((_res) => {
result = _res
loading = false
resolve(_res)
}).catch(reject)
})
}
}
}
const sleep = (period: number) => {
return new Promise((resolve) => {
setTimeout(resolve, period)
})
}
const test = async () => {
const period = 600
console.log("test start waiting")
await sleep(period)
console.log("test has waited")
return 123
}
const testOnce = asyncOnce(test, console.log)
const start = () => {
testOnce().then((res) => {
console.log("first res", res)
})
setTimeout(() => {
testOnce().then((res) => {
console.log("res in setTimeout", res)
})
}, 300)
setTimeout(() => {
testOnce().then((res) => {
console.log("res in a long setTimeout", res)
})
}, 900)
}
start()