JS关于Map和Promise不能持久化(缓存)的问题做处理

200 阅读3分钟

在公司某个场景中使用了Map数据结构,后来遇到需要缓存到sessionStorage的情况,众所周知,Map 对象默认情况下不能直接被 JSON 序列化,这是因为 JSON.stringify 方法只支持序列化原始数据类型(如字符串、数字、布尔值等)、数组和普通的 JavaScript 对象。

const myMap = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(JSON.stringify({ map: myMap })); // 输出 "{"map":{}}"

Map 对象包含了键值对,而这些键或值可能不仅仅是字符串,还可以是其他类型的值,例如函数、对象等。因此,在默认情况下,JSON.stringify 不知道如何处理 Map 对象。

为了能使Map被缓存到sessionStorage可以很快想到用遍历的方式遍历每一项值然后针对每一项值进行序列化,代码看起来大概如下:

 // 假设我们有一个 Map 对象
 const myMap = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  ['key3', { nestedKey: 'nestedValue' }],
 ]);

 // 定义一个函数来序列化 Map 对象
 function serializeMap(map) {
  return Array.from(map.entries()).map(([key, value]) => {
    // 如果值是对象,则递归地进行序列化
    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      value = serializeMap(new Map(Object.entries(value)));
    }
    return { key, value };
  });
 }

 // 序列化 Map 对象并转换为 JSON 字符串
 try {
  const jsonString = JSON.stringify({ map: serializeMap(myMap) });

  // 将 JSON 字符串保存到 sessionStorage
  sessionStorage.setItem('myMap', jsonString);

  console.log('Map successfully serialized and stored in sessionStorage.');
 } catch (error) {
  console.error('Error serializing Map:', error);
 }

这样做确实能满足很多使用场景了,在这个例子中,我们定义了一个 serializeMap 函数,它接收一个 Map 并将其转换成一个数组,其中每个元素都是一个包含 keyvalue 的对象。然后我们在 JSON.stringify 的第二个参数中提供了一个替换函数,用来检查每个值是否为 Map 类型。如果是,就调用 serializeMap 函数进行序列化;否则,返回原来的值。

当然我们也可以使用JSON对象模拟Map,这样的好处就是对新手更友好一点(请慎重考虑JSON和Map之间的本质区别,这里不做阐述)。

 // 使用JSON对象模拟Map
 const jsonMap = {
  key1: 'value1',
  key2: 'value2',
  key3: { nestedKey: 'nestedValue' },
 };

 // 将 JSON 对象转换为 JSON 字符串并保存到 sessionStorage
 try {
  const jsonString = JSON.stringify(jsonMap);
  sessionStorage.setItem('jsonMap', jsonString);
  console.log('JSON object successfully stored in sessionStorage.');
 } catch (error) {
  console.error('Error storing JSON object:', error);
 }

这种方法可以处理包含对象作为值的 Map。如果 Map 中的键也是复杂类型(如对象),你需要修改 serializeMap 函数来处理这种情况。如果键或值是原始数据类型(如字符串、数字等),则不需要额外处理。

请注意,当键不是字符串时。在这种情况下,Map 提供了更强大的功能,而使用 Map 需要进行适当的序列化和反序列化处理。

在这里我就以此为基础,使用JSON对象模拟Map并兼容存储Promise数据,思想也很简单,既判断存入的value是否为Promise示例,如是,则使用占位符,然后使用定时器监听等待Promise返回结果后在次更新即可。

import md5 from 'js-md5';

const PENDING_PROMISE = 'Pending Promise';

const createSerializableMap = (storageKey) => {
  const storedData = sessionStorage.getItem(storageKey);
  const mapData = storedData ? JSON.parse(storedData) : {};

  return {
    has(key) {
      return mapData[md5(key)] !== undefined;
    },
    get(key) {
      const hashKey = md5(key);
      const value = mapData[hashKey];

      // 兼容 Promise
      if (value === PENDING_PROMISE) {
        // 返回一个等待的 Promise 实例
        return new Promise((resolve, reject) => {
          // 检查 Promise 是否已解析
          const interval = setInterval(() => {
            const updatedValue = mapData[hashKey];
            if (updatedValue !== PENDING_PROMISE) {
              clearInterval(interval);
              resolve(updatedValue);
            }
          }, 100);
        });
      } else {
        return value || null;
      }
    },
    set(key, value) {
      const hashKey = md5(key);

      // 兼容 Promise
      if (value instanceof Promise) {
        mapData[hashKey] = PENDING_PROMISE;
        sessionStorage.setItem(storageKey, JSON.stringify(mapData));
        value
          .then((resolvedValue) => {
            mapData[hashKey] = resolvedValue;
            sessionStorage.setItem(storageKey, JSON.stringify(mapData));
          })
          .catch((error) => {
            console.error(`Failed to resolve promise for key "${key}":`, error);
            delete mapData[hashKey];
            sessionStorage.setItem(storageKey, JSON.stringify(mapData));
          });
      } else {
        mapData[hashKey] = value;
        sessionStorage.setItem(storageKey, JSON.stringify(mapData));
      }
    },
    delete(key) {
      const hashKey = md5(key);
      delete mapData[hashKey];
      sessionStorage.setItem(storageKey, JSON.stringify(mapData));
    },
    clear() {
      Object.keys(mapData).forEach((key) => delete mapData[key]);
      sessionStorage.setItem(storageKey, JSON.stringify(mapData));
    },
    getAll() {
      return mapData;
    },
  };
};

// 使用方法
const smap = createSerializableMap('demo001')
smap.set(key,value);

这个例子中,我对存储键进行了MD5计算,这是为了避免键名过长导致的存储空间浪费,至此已经可以满足我的场景所需了,如果你需要考虑更加通用的方法,可以在此基础上完善,谢谢大家。