Dynamics 365表单加载时fetch偶发全局作用域关闭错误如何解决?
错误诱因
你遇到的TypeError: Failed to execute 'fetch' on 'Window': The global scope is shutting down报错,核心原因是触发fetch的宿主全局上下文(也就是你嵌入的HTML Web Resource对应的contentWindow)已经被销毁时,fetch请求仍被发起。
在Dynamics 365表单场景下,该报错通常由以下操作触发:
- 表单刚加载还在初始化Web Resource的过程中,用户直接关闭表单、切换到其他记录、或者切走了Web Resource所在的标签页,导致Web Resource的窗口实例被销毁
- Web Resource加载过程异常中断,你拿到的
contentWindow已经是无效对象,但后续逻辑仍在执行
现有代码问题
- 初始化的轮询逻辑无边界校验:你用
setInterval轮询检测Web Resource内的方法是否就绪,只有找到方法后才会清除轮询,若中途Web Resource被销毁,轮询仍会执行,调用已销毁窗口的方法时就会触发异常 - 未关联表单生命周期:没有绑定表单卸载事件,表单关闭/切换时不会主动中断未完成的初始化逻辑和请求
- fetch请求无中断机制:未使用
AbortController控制fetch请求,上下文销毁后未完成的请求仍在后台运行,回调时就会触发全局作用域关闭的报错
修复方案
步骤1:改造表单初始化逻辑,绑定生命周期事件
在表单侧的脚本中存储轮询ID和请求中断控制器,表单卸载时主动清除轮询、中断未完成的请求:
// 作用域限定在当前表单脚本范围内即可 let uploaderInitInterval = null; let fetchAbortController = null; export function setContextForFileUploader(executionContext) { const formContext = executionContext.getFormContext(); // 绑定表单销毁事件 formContext.ui.addOnDestroy(() => { if (uploaderInitInterval) clearInterval(uploaderInitInterval); if (fetchAbortController) fetchAbortController.abort(); }); const wrControl = formContext.getControl('WebResource_myWebResource'); if (wrControl) { wrControl.getContentWindow() .then(contentWindow => { uploaderInitInterval = self.setInterval(async () => { // 每次轮询先校验上下文有效性 if (!contentWindow || !contentWindow.parent || formContext.ui.isFormClosed()) { clearInterval(uploaderInitInterval); return; } if (contentWindow.setClientApiContext && contentWindow.myNS?.EmailAttachmentUploader) { clearInterval(uploaderInitInterval); fetchAbortController = new AbortController(); contentWindow.setClientApiContext(Xrm, formContext, fetchAbortController); try { await contentWindow.myNS.EmailAttachmentUploader.DocumentLoaded(); } catch(e) { // 忽略主动中断的请求错误 if (e.name !== 'AbortError') { showAlertDialog(e, 300, 600); } } } }, 200); }) .catch(() => { // 捕获getContentWindow异常,清除无效轮询 if (uploaderInitInterval) clearInterval(uploaderInitInterval); }); } }
步骤2:改造Web Resource内的逻辑,支持请求中断
修改setClientApiContext接收中断控制器,fetch调用时绑定中断信号,发起请求前先校验上下文有效性:
let globalAbortController = null; function setClientApiContext(xrm, formContext, abortController) { window.myXrm = xrm; window.myFormContext = formContext; globalAbortController = abortController; }
修改PopulateEntityInfo的fetch逻辑:
function PopulateEntityInfo() { // 上下文无效或请求已被中断时直接返回 if (!window || globalAbortController?.signal.aborted) { return Promise.resolve(); } const query = 'EntityDefinitions?$select=EntitySetName,LogicalName&$filter=ObjectTypeCode eq ' + (window as any).entityTypecode; return fetch(encodeURI('API_URL' + webApiQuery), { signal: globalAbortController.signal }) .then(async (response) => { if (response.ok) { const data = await response.json(); if (data != null) { (window as any).entityCollectionName = data.value[0].EntitySetName; (window as any).logicalName = data.value[0].LogicalName; } } else { showAlertDialog( 'There was a problem fetching entity definitions.', 300, 600 ); } }) .catch((error) => { if (error.name !== 'AbortError') { showAlertDialog(error, 300, 600); } }); }
可选优化
- 替换轮询逻辑:可以直接绑定Web Resource控件的
onLoad事件检测资源加载完成,替代setInterval轮询,减少无效执行 - 延迟初始化:如果Web Resource放在非默认标签页中,可以绑定标签页的
TabStateChange事件,只有用户切到对应标签页时再执行初始化逻辑,避免无效的资源调用
内容的提问来源于stack exchange,提问作者Zare
相关产品推荐
相关产品推荐

