多Adobe Target实验同页面复用sessionStorage实现单次API调用异常排查
问题根因
两个并行加载的实验脚本触发重复API请求,本质是两个时序问题叠加出来的:
- 异步逻辑时序错位:两个实验script是同步加载执行的,但API请求是异步操作。第一个脚本运行时,会立刻查sessionStorage发现没缓存,随即触发异步API请求——这时候脚本的同步逻辑就走完了,根本不会等API返回、把结果写进sessionStorage。浏览器紧接着执行第二个实验脚本时,第一个脚本的API请求还在等待网络响应,sessionStorage里自然没有
sparksSSO的内容,第二个脚本就会判定无缓存、重新发起请求。 - 全局变量覆盖冲突:两个脚本都用
var sparksSSO声明全局变量,第二个脚本执行时会直接把第一个脚本的全局变量值覆盖掉,哪怕后续API返回、写入sessionStorage,两个脚本也只会保留自己同步执行阶段拿到的空对象,根本感知不到后续写入的缓存。
你之前逐标签页打开测试完全正常,是因为每次开新标签页时,sessionStorage已经存好了上一次请求完成的结果,不存在并行运行、等待异步请求的时间差,自然能正常命中缓存。
调整方案
要实现同一页面只发一次请求、所有实验复用缓存的目标,核心是把“只查询最终缓存值”的逻辑,改成“能识别正在进行的请求状态”,消除异步时序带来的竞态问题:
- 抽离一套统一的公共取数逻辑,所有实验不要单独实现API请求、sessionStorage读写的代码,全部统一调用这套方法
- 取数时按三级优先级判断,从根源杜绝重复请求:
- 先查sessionStorage里有没有已经存好的
sparksSSO结果,有的话直接返回数据 - 如果没有写入完成的缓存,再检查全局对象(直接挂在window上即可)有没有正在等待返回的API请求Promise,有的话直接复用这个Promise等待结果,绝不重复发请求
- 如果既没有存好的缓存,也没有正在运行的请求,就触发第一次API请求,并且立刻把这个请求的Promise挂到全局对象上,等请求成功返回后,再把序列化好的结果写进sessionStorage
- 先查sessionStorage里有没有已经存好的
- 移除实验脚本里的裸全局变量声明(比如直接写
var sparksSSO = ...),所有实验需要的SSO数据,都从统一取数方法的回调里获取,避免两个脚本的变量互相覆盖。
可直接复用的参考代码
公共取数方法(所有实验共用,仅需实现一次):
function getSparksSSOData() { // 优先读取已完成的缓存 const cachedResult = sessionStorage.getItem('sparksSSO'); if (cachedResult) return Promise.resolve(JSON.parse(cachedResult)); // 复用正在进行的请求,避免重复发起 if (window.__sparksSSORequest) return window.__sparksSSORequest; // 首次发起请求,立刻把Promise挂载到全局 window.__sparksSSORequest = fetch('/你的SSO数据接口地址') .then(res => res.json()) .then(data => { // 拿到结果立刻写入sessionStorage sessionStorage.setItem('sparksSSO', JSON.stringify(data)); return data; }) .catch(error => { // 请求失败时清空全局挂载的Promise,允许后续重试 window.__sparksSSORequest = null; throw error; }); return window.__sparksSSORequest; }
两个实验的业务逻辑直接复用这个方法即可,不需要改动原有DOM操作逻辑:
// EXP33实验逻辑 getSparksSSOData().then(sparksSSO => { const sparksOfferCount = sparksSSO.totalOffers >=0 ? sparksSSO.totalOffers : ''; const sparksAllOffers = sparksSSO.allOffers; // 其余原有实验DOM交互逻辑 }); // EXP78实验逻辑 getSparksSSOData().then(sparksSSO => { const sparksOfferCount = sparksSSO.totalOffers >=0 ? sparksSSO.totalOffers : ''; const sparksAllOffers = sparksSSO.allOffers; // 其余原有实验DOM交互逻辑 });
不管两个实验脚本谁先执行,第一次触发的请求会把Promise挂载到全局,后执行的脚本会直接复用这个等待中的Promise,不会重复发起网络请求,等数据返回后两个实验都会拿到同一份缓存数据执行逻辑。
内容的提问来源于stack exchange,提问作者Chuwa Feet
相关产品推荐
相关产品推荐

