通过拦截器或封装函数在请求发起前缓存数据,并在响应返回后更新备份。具体做法是维护一个全局状态对象,在调用 fetch 之前将当前关键数据存入该对象,随后执行网络请求;若请求成功则根据新结果决定是否覆盖旧备份,若失败则保留原数据不变。

如何设计通用的请求备份中间件
使用高阶函数包装 fetch 是最稳妥的方案。定义一个 withBackup 函数,接收原始 fetch 和备份存储作为参数,内部处理生命周期逻辑。

const backupStore = new Map();
function withBackup(fetchFn, storageKey) {
return async (url, options = {}) => {
// 1. 请求前:尝试从备份中恢复或初始化
const cachedData = backupStore.get(storageKey);
console.log('Request start, cache:', cachedData);
try {
// 2. 执行实际请求
const response = await fetchFn(url, options);
if (!response.ok) throw new Error('Network error');
const data = await response.json();
// 3. 请求成功:更新备份
backupStore.set(storageKey, data);
return { success: true, data };
} catch (error) {
// 4. 请求失败:保持备份不变,可返回旧数据或报错
console.error('Fetch failed, keeping backup', error);
return { success: false, data: cachedData, error };
}
};
}如何处理并发请求导致的备份冲突
当多个请求同时操作同一备份键时,需引入简单的锁机制或版本号控制。这里采用基于 Promise 的简易队列来串行化对同一 Key 的写入操作,确保备份数据的原子性。
立即学习“Java免费学习笔记(深入)”;

class BackupManager {
constructor() {
this.store = new Map();
this.locks = new Map();
}
async requestWithLock(key, fetchUrl) {
// 获取或创建锁
if (!this.locks.has(key)) {
this.locks.set(key, Promise.resolve());
}
// 等待前一个任务完成
const previous = this.locks.get(key);
const current = previous.then(async () => {
// 读取当前备份
const backup = this.store.get(key);
try {
const res = await fetch(fetchUrl);
const data = await res.json();
// 更新备份
this.store.set(key, data);
return data;
} catch (e) {
// 失败不更新备份
return backup;
} finally {
// 释放锁
this.locks.delete(key);
}
});
// 更新锁为当前任务
this.locks.set(key, current.catch(() => {}));
return current;
}
}
const manager = new BackupManager();
manager.requestWithLock('user_data', '/api/user').then(console.log);


















