You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript调用.then()后回调代码不执行问题排查

问题根本原因

核心问题有两个:

  1. .then()是Promise实例专属方法:只有函数返回标准Promise对象时,才能合法调用.then()绑定异步回调。你最初的最简复现里abc()执行return;返回undefined,业务代码里MetaMaskConnect()最终return true返回布尔值,二者都不是Promise类型,本身不具备.then()方法,自然无法触发你绑定的回调。你后来加return Promise.resolve("Success")后代码能跑,只是因为终于返回了合法的Promise实例,满足了.then()的调用前提,但这个改法逻辑上并不正确。
  2. 异步流程完全失控: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 10:21:26