JavaScript调用.then()后回调代码不执行问题排查
问题根本原因
核心问题有两个:
.then()是Promise实例专属方法:只有函数返回标准Promise对象时,才能合法调用.then()绑定异步回调。你最初的最简复现里abc()执行return;返回undefined,业务代码里MetaMaskConnect()最终return true返回布尔值,二者都不是Promise类型,本身不具备.then()方法,自然无法触发你绑定的回调。你后来加return Promise.resolve("Success")后代码能跑,只是因为终于返回了合法的Promise实例,满足了.then()的调用前提,但这个改法逻辑上并不正确。- 异步流程完全失控:
MetaMaskConnect里的message事件监听是异步触发的——你打开弹窗、绑定监听之后函数就执行完了,等监听到消息、拿到钱包地址写入localStorage的操作,和函数主流程完全脱节。就算你返回了Promise,如果在异步操作完成前就提前resolve,.then()里的回调会比事件触发更早执行,读localStorage的时候大概率还是null,根本拿不到钱包地址。
另外你原来的事件移除写法是错误的:removeEventListener第二个参数必须传入和绑定时完全一致的函数引用,你传event对象根本无法移除监听,会导致事件重复绑定、回调多次触发。
规范实现方式
核心思路是把所有异步逻辑包裹在Promise中返回,只有当拿到钱包地址、完成localStorage写入后才resolve,出现异常(比如弹窗被拦截、地址为空)时reject,保证.then()触发时异步操作已经全部完成。
修正后的MetaMaskConnect方法
function MetaMaskConnect() { return new Promise((resolve, reject) => { let connectWindow = null; if (connectWindow === null) { connectWindow = window.open("http://localhost:3300"); // 处理弹窗被浏览器拦截的场景 if (!connectWindow) { reject(new Error("钱包弹窗被拦截,请允许站点弹窗后重试")); return; } } // 握手阶段消息监听 const handleHandshake = (event) => { // 非信任来源消息直接忽略 if (event.origin !== "http://0.0.0.0:8033" || event.data === null) return; // 握手成功,移除当前监听 window.removeEventListener("message", handleHandshake); // 向钱包窗口发地址请求 connectWindow.postMessage("address_request", "http://localhost:3300"); // 地址返回阶段消息监听 const handleAddressResponse = (event) => { if (event.origin !== "http://0.0.0.0:8033") return; if (event.data === null) { window.removeEventListener("message", handleAddressResponse); reject(new Error("获取钱包地址失败")); return; } const address = event.data; console.log("获取到钱包地址: " + address); // 清理监听 window.removeEventListener("message", handleAddressResponse); // 更新本地存储 localStorage.removeItem('metamask_address'); localStorage.setItem('metamask_address', address); // 异步流程完成,返回结果 resolve(address); }; window.addEventListener("message", handleAddressResponse, false); }; window.addEventListener("message", handleHandshake, false); }); }
修正后的调用逻辑
推荐用async/await配合try/catch写异步逻辑,比链式.then()更清晰,同时把重复的加载状态复位逻辑抽离,避免多分支重复写代码:
$(document).on('change', '#metamask-eth-paths', async function(e) { // 抽离公共的加载状态复位逻辑 const resetLoading = () => { $('body').css({'cursor': 'default'}); $('.popup-loader-main-wallet').addClass('hidden'); }; // 错误提示公共逻辑 const showError = () => { resetLoading(); $('#hd-section').empty().append('<h3>An error occured please try again later</h3>'); }; // 初始化加载状态 $('.popup-loader-main-wallet').removeClass('hidden'); $('body').css({'cursor': 'wait'}); const path = $(this).val(); // 原代码if/else分支赋值完全相同,无需判断 const path_txt = "Account"; const type = "eth"; try { // 等待钱包连接、地址获取完成再往下执行 await MetaMaskConnect(); const meta_address = localStorage.getItem('metamask_address'); console.log(meta_address); if (!meta_address) { showError(); return; } $.ajax({ url: '/wallet/get-metamask-hd-address/', type: 'GET', data: { type: type, address: meta_address, add_path: path_txt }, success: function(result) { resetLoading(); if (result) { $('#hd-section').empty().append(result.response_html); $("#hd-wallet-modal").modal('show'); } }, error: showError }) } catch (err) { console.error(err); showError(); } });
内容的提问来源于stack exchange,提问作者2StepsFromHell
相关产品推荐
相关产品推荐

