You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

拦截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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 12:50:23