Webpack打包应用中如何阻塞代码直至异步Fetch完成?
看起来你碰到了异步加载配置时模块导出时机的棘手问题——因为fetch是异步操作,你的runtime.jsx导出的变量在builder.jsx导入时还没被Promise赋值,导致后续依赖配置的黑盒组件拿到undefined直接报错,又没法修改那些组件,确实头疼。
先拆解下你当前代码的核心问题:
runtime.jsx里的fetch是异步任务,exportedResult初始值是undefined,模块执行到export default时直接导出了这个undefined,后续then回调里的赋值根本赶不上其他模块的导入节奏。- 用
Promise.then()重构也没用,因为模块导出是即时完成的,不会等待异步回调执行,所以导出的始终是初始的undefined。
下面给你两个可行的解决方案,优先推荐第一个(现代环境友好):
方案1:使用ES模块的顶级await(推荐)
现代浏览器、Webpack、Vite等主流工具都支持顶级await,它能让模块等待Promise完成后再完成加载,这样其他模块导入时拿到的就是解析后的完整配置对象,而不是undefined。
修改你的代码如下:
runtime.jsx
// 注意:这里不要用require,fetch直接使用文件的URI路径 const response = await fetch('/path/to/config.settings.json'); // 等待JSON解析完成 const configData = await response.json(); // 直接导出解析后的配置对象 export default configData;
builder.jsx
import config from './runtime.jsx'; // 现在config已经是解析后的JSON对象,不会是undefined了 const globalSettings = { taskParameter: config.taskParams, someHardcodeValue: "avalue" }; export default globalSettings;
关键:入口文件需等待配置加载完成再初始化应用
因为使用了顶级await的模块会变成异步模块,所以你的应用入口需要等待配置加载完成后再初始化那些依赖配置的黑盒组件。比如:
// app.jsx(应用入口) async function initApplication() { // 等待builder模块加载完成(也就是配置加载完成) const { default: globalSettings } = await import('./builder.jsx'); // 在这里初始化你的应用,比如React渲染 ReactDOM.render( <App settings={globalSettings} />, document.getElementById('root') ); } // 启动初始化流程 initApplication();
这个方案不会阻塞主线程,只是让模块加载等待异步操作完成,用户体验和性能都不受影响。
方案2:使用同步XMLHttpRequest(不推荐)
如果你必须支持非常旧的环境,没办法用顶级await,可以用同步的XHR来加载配置——但要注意,同步XHR会阻塞整个浏览器主线程,直到请求完成,这会导致页面卡顿、无响应,所以仅作为兜底方案:
runtime.jsx
let exportedResult; const xhr = new XMLHttpRequest(); // 第三个参数设为false,开启同步模式 xhr.open('GET', '/path/to/config.settings.json', false); xhr.send(); if (xhr.status === 200) { // 同步解析JSON exportedResult = JSON.parse(xhr.responseText); } export default exportedResult;
这样builder.jsx导入的config就是已经解析好的配置对象,不需要处理异步,但代价是页面会在加载配置时卡住,所以尽量避免使用这个方法。
为什么原来的Promise.then()方案无效?
再补充下核心原因:ES模块的导出是静态绑定的,模块执行到export语句时就会确定导出的值,后续异步回调里的赋值不会回溯修改已经导出的内容。所以你之前的then里给exportedResult赋值时,builder.jsx早就已经导入了初始的undefined,自然会报错。
内容的提问来源于stack exchange,提问作者Yumbelie

