该分析追踪器初始化脚本是否存在竞态条件?
问题解答
关于两个核心疑问的直接结论
foo()会不会在脚本加载完成前被调用?
当然会。因为初始化代码里给脚本加了async=true,浏览器会异步加载这个脚本,不会等它下载执行完再跑后续代码。而app.initialized是在创建完脚本标签后立刻设为true,这时候脚本十有八九还没加载好,foo()自然还没被真正挂载到命名空间上。如果后续调用foo()的代码在脚本加载完成前执行,就会触发对“未就绪的foo()”的调用。app.initialized是不是没意义?
单看你贴出来的两段代码,它确实没起到判断foo()是否可用的作用——它只表示“初始化流程已经启动了”,不代表“脚本加载完、函数能用了”。但结合补充说明里的存根队列机制来看,它的作用是防止重复触发初始化流程,并非完全没用。
代码逻辑拆解
初始化代码的问题点
const app = window[NAMESPACE]; if (!app.initialized) { const script = document.createElement('script'); script.async = true; script.src = `${script_url}?namespace=${NAMESPACE}`; document.getElementsByTagName('script')[0].appendChild(script); app.initialized = true; // 这里直接标记为已初始化,完全没等脚本加载完成 }
这里的关键问题是app.initialized的赋值时机:异步脚本的下载和执行是独立于当前同步代码的,所以设置app.initialized = true的时候,脚本大概率还在下载中,对应的函数都还没准备好。
调用代码的逻辑缺陷
if (app.initialized) { window[NAMESPACE].foo(....) }
这段代码的判断逻辑完全不对——它只检查“有没有启动初始化”,不检查“foo()是不是真的能调用”,所以必然会出现app.initialized为true但foo()还没就绪的场景。
补充说明的兜底机制详解
根据和原方案团队的沟通,实际代码里有个很关键的兜底逻辑:
- 在初始化命名空间的时候,会提前给
foo()这类函数设置存根函数——说白了就是个占位的空函数,内部会把这次调用的参数、上下文都加到一个队列里存起来 - 当异步脚本真正加载完成后,会把存根函数替换成真正的
foo(),然后批量执行队列里攒下来的所有调用 - 这时候
app.initialized的作用就明确了:防止多次创建脚本标签、重复触发初始化流程,确保整个追踪器只被初始化一次
这种设计是异步加载第三方工具(比如埋点、分析追踪器)的常见操作,既能避免重复初始化的浪费,又能保证提前发起的调用不会直接报错,而是被延后执行,不会丢数据。
内容的提问来源于stack exchange,提问作者rrauenza
相关产品推荐
相关产品推荐

