在现代Web开发中,高效的资源管理和请求处理对于提升用户体验至关重要。特别是在涉及大量API调用或数据获取的应用场景下,减少不必要的网络请求和重复工作可以显著提升应用性能。
复用同时发起的请求
要实现在请求发起时就缓存请求,并让同时发起的相同请求能够复用结果,我们可以采用一种机制来"锁定"正在处理中的请求,并让后续的相同请求等待这个请求完成,然后共享其结果。这通常被称为"请求防抖(debounce)“或"请求节流(throttle)“的一种变体,更具体地,是一种并发控制策略。
并发控制
- 请求队列:维护一个请求队列或映射表,记录正在处理中的请求及其对应的回调函数。
- 锁机制:当一个请求开始时,检查是否已有相同请求在处理中。如果有,则将新的回调加入等待列表;如果没有,则启动新的请求,并将此请求标记为“处理中”。
下面是一个简化的示例,展示了如何在 Axios 请求发出前检查是否有相同的请求正在进行,并等待该请求完成,从而复用结果:
// axiosWithConcurrentCache.js
const axios = require('axios');
const LRU = require('lru-cache');
const cache = new LRU({ max: 100, maxAge: 1000 * 60 });
const pendingRequests = new Map(); // 用于存储正在处理中的请求
// 封装的请求函数,实现并发控制和缓存
async function requestWithCache(config) {
// 生成请求的唯一标识
const requestKey = JSON.stringify(config);
// 首先检查缓存
if (cache.has(requestKey)) {
console.log(`Using cached response for ${requestKey}`);
return cache.get(requestKey); // 直接返回缓存结果
}
// 检查是否有相同请求正在进行
if (pendingRequests.has(requestKey)) {
console.log(`Waiting for ongoing request for ${requestKey}`);
// 等待正在进行的请求完成
return new Promise((resolve, reject) => {
pendingRequests.get(requestKey).push({ resolve, reject });
});
}
// 否则,创建一个新的Promise来代表这个请求,并将其加入到pendingRequests中
const promiseChain = axios(config)
.then(response => {
// 成功后,缓存结果并解析所有等待此请求的Promise
cache.set(requestKey, response);
pendingRequests.get(requestKey).forEach(p => p.resolve(response));
pendingRequests.delete(requestKey);
return response;
})
.catch(error => {
// 错误处理,同样解析所有等待此请求的Promise
pendingRequests.get(requestKey).forEach(p => p.reject(error));
pendingRequests.delete(requestKey);
throw error;
});
// 将新创建的Promise链存储起来,并返回一个新的Promise给当前调用者
pendingRequests.set(requestKey, []);
return new Promise((resolve, reject) => {
pendingRequests.get(requestKey).push({ resolve, reject });
}).then(() => promiseChain);
}
module.exports = { requestWithCache };
现在,你可以这样使用封装好的请求函数:
// 在其他文件中使用封装的请求函数
const { requestWithCache } = require('./axiosWithConcurrentCache');
requestWithCache({
method: 'get',
url: 'https://api.example.com/data'
})
.then(response => {
console.log('Data received:', response.data);
})
.catch(error => {
console.error('Error fetching data:', error);
});
这个模式确保了对于同一个请求,在任何给定的时间内,只有一个实际的网络请求会被发送,而其他相同请求则会等待这个结果并复用它。这样既减少了服务器压力,又提高了客户端的响应速度。