Javascript异步缓存和任务链

981 阅读2分钟

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函数是递归的,一次调用会请求队列中的任务。