拦截esri-leaflet的XHR请求后,CacheStorage缓存数据无法渲染问题
问题:拦截XHR实现ArcGIS要素图层缓存失效
基于esri-leaflet开发的Web应用,通过XHR而非fetch从ArcGIS Server加载GeoJSON数据。尝试拦截XHR请求,在存在缓存时返回CacheStorage中的要素图层数据,但代码无法生效:数据可正常存入CacheStorage并成功读取,但无论如何配置XHR响应,浏览器始终不认可伪造的XHR请求成功,也不会将缓存数据反馈给应用进行渲染。
原代码如下:
const cache = await caches.open('my-cache'); (function() { var origOpen = XMLHttpRequest.prototype.open; var origSend = XMLHttpRequest.prototype.send; var targeturl, rs; XMLHttpRequest.prototype.send = async function() { var self = this; // Check for cached response before sending the request const cachedResponse = await caches.match(targeturl, { //ignoreSearch: true, //true will ignore querystring ignoreMethod: true, ignoreVary: true }); if (cachedResponse) { // Use cached response rs = await cachedResponse.json(); Object.defineProperty(self, 'status', { writable: true, configurable: true }); Object.defineProperty(self, 'readyState', { writable: true, configurable: true }); Object.defineProperty(self, 'responseText', { writable: true, configurable: true }); Object.defineProperty(self, 'responseURL', { writable: true, configurable: true }); Object.defineProperty(self, 'response', { writable: true, configurable: true }); self.status = 200; // Set appropriate status code self.readyState = 4; self.responseText = JSON.stringify(rs); // Access cached response text self.response = JSON.stringify(rs); self.responseURL = targeturl; self.dispatchEvent(new Event('loadstart')); self.dispatchEvent(new Event('load')); self.dispatchEvent(new Event('loadend')); } else { // Send the actual XHR request origSend.apply(self, arguments); } }; XMLHttpRequest.prototype.open = function(method, url) { targeturl = url; var self = this; // Corrected event listener logic self.addEventListener('loadstart', async (e) => { console.log('request started!'); //try set response again as not working e.target.status = 200; // Set appropriate status code e.target.readyState = 4; e.target.responseText = JSON.stringify(rs); // Access cached response text console.log(e) }); self.addEventListener('load', async (e) => { console.log('request completed!', e, rs); // Add response to cache if not already cached const cacheKeys = await cache.keys(); const hit = cacheKeys.find((key) => key.url === url); if (!hit) { await cache.add(url); // Cache the actual response } }); origOpen.apply(self, arguments); }; })();
解决方案
原代码核心问题是未模拟XHR原生的状态变更流程,且全局变量会引发并发请求冲突,以下是修正后的实现:
关键修改点
- 移除全局变量
targeturl和rs,改用XHR实例的自定义属性存储请求信息,避免多请求干扰 - 严格遵循XHR原生
readyState的变更顺序(1→2→3→4)触发对应事件 - 把异步逻辑封装在立即执行函数中,不破坏
send方法的原生同步调用约定 - 批量修改XHR属性的可写性,确保属性能被正确赋值
- 调整事件触发顺序,匹配原生XHR的事件流
修正后的代码
const cache = await caches.open('my-cache'); (function() { const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url) { // 给当前XHR实例绑定请求信息,替代全局变量 this._requestUrl = url; this._requestMethod = method; origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function() { const self = this; // 用立即执行异步函数处理缓存逻辑,不阻塞主线程 (async function handleCache() { const cachedResponse = await caches.match(self._requestUrl, { ignoreMethod: true, ignoreVary: true }); if (cachedResponse) { const cachedData = await cachedResponse.json(); const jsonStr = JSON.stringify(cachedData); // 批量开启XHR属性的可写/可配置权限 Object.defineProperties(self, { status: { writable: true, configurable: true }, readyState: { writable: true, configurable: true }, responseText: { writable: true, configurable: true }, responseURL: { writable: true, configurable: true }, response: { writable: true, configurable: true }, statusText: { writable: true, configurable: true } }); // 模拟原生XHR完整状态流转 self.readyState = 1; // OPENED self.dispatchEvent(new Event('readystatechange')); self.readyState = 2; // HEADERS_RECEIVED self.status = 200; self.statusText = 'OK'; self.responseURL = self._requestUrl; self.dispatchEvent(new Event('readystatechange')); self.readyState = 3; // LOADING self.responseText = jsonStr; self.response = cachedData; self.dispatchEvent(new Event('readystatechange')); self.readyState = 4; // DONE self.dispatchEvent(new Event('readystatechange')); self.dispatchEvent(new Event('load')); self.dispatchEvent(new Event('loadend')); } else { // 无缓存时发送真实请求,并缓存响应 origSend.apply(self, arguments); self.addEventListener('load', async () => { const cacheKeys = await cache.keys(); const isCached = cacheKeys.some(key => key.url === self._requestUrl); if (!isCached) { await cache.add(self._requestUrl); } }); } })(); }; })();
额外说明
- esri-leaflet内部依赖
readystatechange事件而非仅load事件,必须完整模拟readyState的每一步变更 - 部分浏览器对XHR属性修改有严格限制,必须通过
Object.defineProperties显式开启可写性 - 异步逻辑封装在立即执行函数中,避免修改
send方法的原生返回值类型
内容的提问来源于stack exchange,提问作者lpfy
相关产品推荐
相关产品推荐

