利用 Promise 的 cache 特性实现前端缓存

我们知道,对一个 Promise 来说,有如下特性:

const promise = new Promise((resolve, reject) => {
    console.log('running');

    setTimeout(() => {
        resolve('something out');
    }, 1000);
});

promise.then((result) => {
    console.log('first then:' + result);
});

promise.then((result) => {
    console.log('second then:' + result);
});

// 最终运行结果是:
// running
// first then: something out
// second then: something out

一个 promise 本身只会 resolve() 一次,在 new Promise(executor) 中它的 executor 也只会执行一次。但是对一个 promise 可以多次调用它的 then() 方法,不管它有没有被 resolve 或者 reject 过。

也就是说 Promise 会“记住”自己被 resolve 或者 reject 的结果,每当自己的 then() 被调用,都会将保存的结果返回给 then() 的回调函数。

原始实现

利用这个特性,我们能够想到,如果一个前端数据请求返回的是一个 promise,那么就很容易利用这个特性实现数据缓存:

const button = document.getElementById('button');
let request;

button.addEventListener('click', () => {
    if (!request) {
        request = axios.get('/get/something');
    }
    request.then((result) => {
        alert(result);
    });
});

进一步完善

考虑如下情况:

  • 对同一个请求可能每次请求的参数不同,我需要针对不同的参数缓存不同的结果
  • 我的缓存必须保证新鲜度,如果缓存时间过久需要重新请求
  • 如果第一次请求失败,我不应该将失败的结果缓存下来
  • 在某些情况下我想主动清除缓存

我们能够设计出这样的 API:

function memorize(fn, opts) {
    opts = {
        cacheKeyFn: defaultCacheKeyFn,
        maxAge: undefined,
        cache: {},
        ...opts
    };

    const memorized = function (...args) {
        // ...
    };

    memorized.clear = function () {
        opts.cache = {};
    };

    return memorized;
}

// 用例如下:
const request = (userId, page){
    return axios.get(`/get/something/${userId}?page=${page});
};

const memorizedRequest = memorize(request, {
    // 有效期 10s
    maxAge: 10 * 1000 
});
// 清除缓存
memorizedRequest.clear();

这样一个简单的前端缓存库就完成了。

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。