Web 从后台获取数据的时候,如果参数和结果都相同,那我们可以将请求缓存起来以节约流量。
Lodash 就提供了相关的函数 memoize,它是以函数为操作单位的。
异步缓存
如果我们希望对缓存进行更加精细的操作,可以这样实现:
const cache = {}
const request = name => new Promise(resolve => setTimeout(() => name, 1000))
function get(name) {
if (!cache[name]) {
return request(name).then(res => {
cache[name] = res
return res
})
}
return cache[name]
}
在这里我们用request函数模拟一个请求。cache是缓存对象,如果有响应name的成员,那么返回这个成员值。如果没有就返回一个 Promise,在 Promise 完成时记录下响应以供下次调用时立即返回。
这样的实现有个问题是:函数的返回结果有两种类型————响应值类型和 Promise 类型,给调用处增加了麻烦。
我们可以把结果统一为 Promise,并用 async/await 语法简化:
const cache = {}
const request = name => new Promise(resolve => setTimeout(() => name, 1000))
async function get(name) {
if (!cache[name]) {
cache[name] = request(name)
}
return await cache[name]
}
这样,保存在 cache中的都是 Promise。完成的 Promise 直接返回响应,但是对于函数还是 Promise;还没完成的 Promise 则等待。
任务链
如果请求无法并发进行(比如请求的顺序有要求),那么我们可以创建一个任务链:
const cache = {}
const queue = []
let running = false
const request = name => new Promise(resolve => setTimeout(() => name, 1000))
async function run() {
let task = queue.shift()
if (task) {
running = true
try {
let res = await request(task.name)
task.resolve(res)
} catch (er) {
task.reject(er)
}
run()
} else {
running = false
}
}
async function get(name) {
if (!cache[name]) {
cache[address] = new Promise((resolve, reject) => {
queue.push({
name,
resolve,
reject
})
})
}
return await cache[name]
}
cache里仍然保存的是 Promise,但是具体的请求函数没有立即执行,而是将参数和 Promise 的 resolve 和 reject 一起推入队列 queue中,等待执行。
每一次 get 函数调用时,先通过running变量检查是否已经在运行,没有则执行run函数。run函数执行了请求函数,获得响应后调用在队列中保存的 resolve 或 reject 函数,这样保存在 cache 中的 Promise 得以完成。同时,run函数是递归的,一次调用会请求队列中的任务。