使用JS Interop从C#调用JS函数时出现跨浏览器函数未定义错误
Blazor JS Interop 调用window异步函数跨浏览器未定义问题解决
问题描述
我在Blazor中通过JS Interop从C#调用挂载在window对象上的异步JS函数,定义的JS函数如下:
第一个函数getAccount:
window.getAccount = async function () { try { if (provider === null) { return { "Success": false, "Address": null, "Message": "Metamask not detected" }; } else { const accounts = await provider.request({ method: "eth_requestAccounts" }); console.log("connected accounts = ", accounts); if (accounts.length !== 0 || accounts !== undefined) { return { "Success": true, "Message": null, "Address": accounts[0].toString() }; } else return "new error"; } } catch (err) { if (err.code === 4001) { return { "Success": false, "Address": null, "Message": "Allow to connect to Metamask" }; } else { console.error(err); return { "Success": false, "Address": null, "Message": err.toString() }; } } }
第二个函数getChainId:
window.getChainId = async function () { return chainId.toString(); }
C#调用代码:
ConnectWallet connectResponse = await JsRuntime.InvokeAsync<ConnectWallet>("getAccount");
string chainId = await JsRuntime.InvokeAsync<string>("getChainId");
遇到的问题:Edge、Firefox中频繁出现**函数未定义(undefined)**错误,Chrome偶尔也会出现单个函数返回未定义的情况。
解决方案
1. 确保JS代码在Blazor初始化完成后挂载
Blazor框架初始化过程中,window对象或JS运行时可能未完全就绪,导致C#调用时函数还未挂载到window上。
- 将自定义JS代码放在Blazor框架脚本之后,放在页面
body末尾(WebAssembly项目修改wwwroot/index.html,Server项目修改Pages/_Host.cshtml):
<!-- Blazor框架脚本 --> <script src="_framework/blazor.webassembly.js"></script> <!-- 你的自定义JS代码 --> <script> // 这里放置window.getAccount和window.getChainId的定义 </script>
- 组件中通过
OnAfterRenderAsync生命周期方法,确保首次渲染完成后再调用JS函数:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // 首次渲染完成后执行JS调用 ConnectWallet connectResponse = await JsRuntime.InvokeAsync<ConnectWallet>("getAccount"); string chainId = await JsRuntime.InvokeAsync<string>("getChainId"); } }
2. 修复全局变量依赖问题
getAccount和getChainId依赖全局变量provider和chainId,如果这些变量未初始化或获取失败,会导致函数逻辑异常,甚至间接引发"函数未定义"的假象。
- 提前初始化全局变量并增加空值检查:
// 初始化provider和chainId let provider = window.ethereum ?? null; let chainId = null; // 提前获取chainId async function initChainId() { if (provider) { chainId = await provider.request({ method: 'eth_chainId' }); } } initChainId(); window.getAccount = async function () { try { if (!provider) { return { "Success": false, "Address": null, "Message": "Metamask not detected" }; } const accounts = await provider.request({ method: "eth_requestAccounts" }); console.log("connected accounts = ", accounts); if (accounts?.length > 0) { // 使用可选链操作符避免undefined报错 return { "Success": true, "Message": null, "Address": accounts[0].toString() }; } else { return { "Success": false, "Address": null, "Message": "No accounts found" }; } } catch (err) { if (err.code === 4001) { return { "Success": false, "Address": null, "Message": "Allow to connect to Metamask" }; } else { return { "Success": false, "Address": null, "Message": err.toString() }; } } } window.getChainId = async function () { if (!chainId && provider) { chainId = await provider.request({ method: 'eth_chainId' }); } return (chainId ?? '0').toString(); }
3. 确保函数挂载的原子性
使用立即执行函数表达式(IIFE)包裹JS代码,确保函数同步挂载到window对象,避免异步执行导致的挂载延迟:
(function() { let provider = window.ethereum ?? null; let chainId = null; async function initChainId() { if (provider) { chainId = await provider.request({ method: 'eth_chainId' }); } } initChainId(); window.getAccount = async function () { // 原有逻辑... } window.getChainId = async function () { // 原有逻辑... } })();
4. 统一返回值类型
getAccount函数中部分分支返回字符串"new error",但C#期望反序列化为ConnectWallet对象,会导致反序列化失败,间接表现为调用异常。确保所有分支返回结构一致的对象:
// 将返回"new error"的分支修改为: return { "Success": false, "Address": null, "Message": "No accounts returned" };
5. 调试函数挂载状态
在浏览器控制台执行console.log(window.getAccount, window.getChainId),确认函数是否存在:
- 如果函数不存在:检查JS代码的加载顺序和执行时机
- 如果函数存在但调用报错:检查Blazor JS运行时的上下文绑定情况
内容的提问来源于stack exchange,提问作者Shubhankar Banerjee
相关产品推荐
相关产品推荐

