Forever Functional: Memoizing Promises

29 阅读4分钟

记忆意味着两个额外的成本:您需要额外的缓存内存来存储所有已计算的值,并且您需要一些额外的时间来检查和使用缓存本身

普通函数

const memoize = (fn) => {
2  const cache = new Map();
3  return (...args) => {
4    const strX = JSON.stringify(args);
5    if (!cache.has(strX)) {
6      cache.set(strX, fn(...args));
7    }
8    return cache.get(strX);
9  };
10}

在 Vue 中,你有计算属性,他们的文档说“计算属性是根据它们的反应性依赖关系缓存的[并且]只会在它的一些反应性依赖关系发生变化时重新评估。 ” 类似地,Vuex中用于计算值的 getter文档提到“ getter 的结果基于其依赖项进行缓存,并且仅在其某些依赖项发生更改时才会重新评估**”。这两个引用并不意味着记忆:只是 Vue 足够聪明,不会在其依赖项都没有改变的情况下重做计算值的计算:如果计算值依赖于属性 X、Y 和 Z,Vue 不会除非 X、Y 或 Z 发生变化,否则重新计算计算值;同时,它将缓存先前计算的值。

React 中也存在类似的问题。useMemo()钩子说它“ useMemo只会在其中一个依赖项发生变化时重新计算记忆值”——本质上与 Vue 所做的相同,但实际上不是记忆化;它只记住一个先前的计算。再说一次,useCallback()做同样的有限缓存,很明显,因为useCallback(fn, dependencies)相当于useMemo(()=>fn, dependencies). 最后,React.memo(...)还做了一个类似简化的缓存优化,如果组件的 props 与上次调用中的相同,则避免重绘组件——但它并没有做记忆化,只是缓存一个以前的结果。

API 调用执行某些 I/O 的函数

废弃的优化

  • 修改服务器以允许缓存,在有些情况下不适用
  • 添加缓存(可能使用CacheStorage)并在任何地方修改代码以在实际调用之前检查缓存,但这不是一个好主意:要做的更改太多,错误的可能性太多!
  • 不是添加新缓存,使用全局存储来代替,检查存储,但是仍然需要以重要的方式更改代码,添加错误的可能性不会消失

我们真正想要的是一种解决方案,它需要尽可能少地更改代码,并且不需要添加任何手写的特殊代码来测试可用数据

promise简介

并发可以直接使用 Promise.all 进行多个 Promise 并发执行

按顺序执行:

        1.可以使用 async 函数里面的 for of 进行迭代

        2.可以使用 reduce 函数进行迭代调用

顺序循环为何要用for···of而不是forEach

因为 for...of 内部处理的机制和 forEach 不同,forEach 是直接调用回调函数,for...of 是通过迭代器的方式去遍历。

while (index < arr.length) {
    // 也就是我们传入的回调函数
    callback(item, index)
}
 
async function test() {
  let arr = [3, 2, 1]
  const iterator = arr[Symbol.iterator]()  //for of会自动调用遍历器函数
  let res = iterator.next()
  while (!res.done) {
    const value = res.value
    const res1 = await fetch(value)
    console.log(res1)
    res = iterator.next()
  }
  console.log('end')
}

利用 Promise 缓存 处理并发问题

const userPromisesCache = new Map<string, Promise<User>>();
 
const getUserById = (userId: string): Promise<User> => {
  if (!userPromisesCache.has(userId)) {
    const userPromise = request.get(`https://users-service/v1/${userId}`);
    userPromisesCache.set(userId, userPromise);
  }
 
  return userPromisesCache.get(userId)!;
};

也可以借助lodash

import memoize from 'memoizee';
 
const getUserById = memoize(async (userId: string): Promise<User> => {
  const user = await request.get(`https://users-service/${userId}`);
  return user;
}, { promise: true});

参考

预取和 SWR 的“双重打击”技术

const getCachedData = memoize(getData);
const memoize = (fn) => {
2  const cache = new Map();
3  return (...args) => {
4    const strX = JSON.stringify(args);
5    if (!cache.has(strX)) {
6      cache.set(
7        strX,
8        fn(...args).catch((err) => {
9          cache.delete(strX);
10          return err;
11        })
12      );
13    }
14    return cache.get(strX);
15  };
16};

SWR 模式

SWR ( Stale While Revalidate ) 模式也可以让你的页面看起来更快、响应更快的第二种模式。关键思想是,如果你请求一些数据,并且 promise 已经被缓存,你会返回当前数据,但同时你会发起一个新的 API 请求来刷新(可能是陈旧的)内存中的数据以供将来使用。您可以通过检查缓存数据的陈旧性并使用它来决定是否立即返回数据来获得更多幻想,但作为第一次尝试,所描述的逻辑会做。请注意,这不是简单的缓存:使用我们的记忆解决方案,API 调用只会发出一次,而使用 SWR API 调用将始终发出,即使调用者立即收到结果。 blog.openreplay.com/forever-fun…

为什么不使用浏览器缓存呢

浏览器缓存对于静态内容非常有效,但除非后端为 HTTP 标头提供了良好的值,例如max-agestale-while-revalidate真的是自己一个人(此外,浏览器使用缓存来快速返回数据,但不会刷新其缓存) . 当然,忽略缓存并发出将跳过它的请求总是可能的(因此,根本没有缓存)。但是,用户不会看到任何速度优势。SWR 是一种中间解决方案,在大多数情况下都能很好地工作,并且 memoizing promises 允许它。