社区精选|脚本执行顺序引发的惨案
SegmentFault
共 6701字,需浏览 14分钟
·
2023-09-08 03:01
今天小编为大家带来的是社区作者 记得要微笑 的文章,让我们一起来看看这场脚本执行顺序引发的惨案🐶
SRE
(系统可靠性工程师)进行处理。令人困惑的是,SRE
在访问生产环境的"查看报价页面"时发现一切正常。
SRE
向用户申请了远程操作权限。在远程操作期间,并没有发现浏览器控制台中存在错误日志,所有页面所依赖的JavaScript
和CSS
脚本都能够正常加载,但加载完成后并没有执行挂载页面模块的操作。因此,初步猜测问题可能出现在页面路由加载方面,导致页面无法正常挂载。
react-router
模块进行路由配置的部分代码,同时在路由加载过程中进行数据采集:
component: Loadable({
loader: () => import('@/pages/inquiry-detail-by-brand').then((comp) => {
/** 页面访问 PV、UV */
debugger;
if ('cassSensorsTrack' in window) {
(window as any).cassSensorsTrack({
eventName: BURY_EVENT_NAME.QUOTATION_RESULT_PAGE_VIEW_CLICK,
desc: '页面浏览',
eventType: 'click',
eventData: '',
});
}
return comp;
}),
loading: Loading,
})
怀疑页面组件模块comp
没有正确挂载到页面上,在then
回调函数中添加断点来验证自己的猜想。
经过调试,发现问题是在脚本执行时,cassSensors 对象未定义,导致无法调用 track 方法。此外,外部代码没有包含异常处理的 try...catch 块。然而,在使用 Loadable 组件时,它内部捕获了该异常,并渲染了空内容。
// 加载函数
function load(loader) {
var promise = loader();
var state = {
loading: true,
loaded: null,
error: null
};
state.promise = promise.then(function (loaded) {
state.loading = false;
state.loaded = loaded;
return loaded;
}).catch(function (err) {
// 捕获异常
state.loading = false;
state.error = err;
throw err;
});
return state;
}
// render
LoadableComponent.prototype.render = function render() {
if (this.state.loading || this.state.error) {
return React.createElement(opts.loading, {
isLoading: this.state.loading,
pastDelay: this.state.pastDelay,
timedOut: this.state.timedOut,
error: this.state.error,
retry: this.retry
});
} else if (this.state.loaded) {
return opts.render(this.state.loaded, this.props);
} else {
return null;
}
};
为什么在加载页面组件脚本后获取不到 cassSensors
变量呢?
注:上述脚本加载顺序是模拟出来的场景,跟实际脚本加载顺序一致
不难发现问题是由于:
3.72881c2b.chunk.js
和 4.e6995327.chunk.js
在 sensorsdata1.19.4.min.js
之前加载和执行,导致在执行 .then
回调时无法访问到 cassSensors
变量。
这是因为:
cassSensors
是由 sensorsdata1.19.4.min.js
在全局作用域中注入的,而在回调执行时它尚未定义,从而导致异常。
cassSensors
,可以采用以下优化方案。在异步加载完成之前,先给出一个结构变量定义,让应用方调用不会报错。同时,可以在页面脚本中创建一个变量来缓存需要采集的数据,等待脚本加载完成后再进行上报操作。
// cassSensors.js
var dataCache = [];
// 在异步脚本脚本完之前先给出结构变量定义,让调用方不会执行报错
if (!"cassSensors" in window) {
window.cassSensors = {
track: function() {},
// ...
}
}
// 异步脚本加载完成后,重新抛出变量,并且上报缓存中的数据
script.onload = fucntion() {
window.cassSensors = window['sensorsDataAnalytic201505'];
window.cassSensors.init();
// ...
if (dataCache.length) {
dataCache.forEach(function(dc) {
window.cassSensors.track(dc); // 上报
});
dataCache = [];
}
}
cassSensors
,避免了报错。同时,通过缓存数据并在加载完成后进行上报,确保页面脚本能够正确获取到cassSensors
变量,并且不会因为加载顺序问题而导致异常。
npm
包的形式进行发版、按需引入和构建打包到业务代码中。
Fiddler
反向代理来延迟加载 sensorsdata1.19.4.min.js
文件,以使页面脚本的执行在 sensorsdata1.19.4.min.js
之前。
Fiddler
破解版的同学可以使用 Nginx
来模拟该场景,并通过第三方的 echo-nginx-module
模块进行配置,以实现延迟响应的反向代理。具体安装步骤可以参考:https://www.cnblogs.com/52fhy/p/10166333.html
http {
listen 8081;
server_name localhost;
# 其他配置项...
server {
# 其他 server 配置项...
location /proxy/ {
rewrite ^/proxy/(.*)$ /$1 break;
proxy_pass https://mstatic.cassmall.com;
# 设置头部内容编码类型,防止返回乱码
proxy_set_header Accept-Encoding "";
proxy_set_header Accept-Language $http_accept_language;
proxy_set_header Content-Type "text/javascript; charset=utf-8";
}
location /delay {
# prelight request
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# 设置反向代理目标服务器
# proxy_pass http://your_backend_server;
echo_location /proxy/$uri;
# 设置延迟时间为 5 秒钟
set $delay 5;
# 在代理响应之前延迟 5 秒钟
echo_sleep $delay;
}
}
}
echo_sleep 与 proxy_pass 配置会有冲突,两者同时配置,只有一个会生效,所以此处使用 echo_location。
往期推荐
社区精选|“奇怪”的 Axios 拦截器
社区精选|纳尼!CSS 也能实现碰撞检测?
社区精选|【NestJS 系列】DI 依赖注入与 IOC 控制反转
评论