PAYATHON 2026

Mocha + Node.js + Axios 中 async/await 未等待请求

支付老李

结论

await 只会暂停当前 async 函数,不会自动让上层调用者等待。

这段代码的异步调用链断在了两个地方:

  • Controller 调用了 submitPaymentsPartialRefundApi(),却没有使用 await,也没有返回它的 Promise。
  • Mocha 的 it block 既没有返回 Promise,也没有声明为 async。Mocha 会把它当作同步代码,执行后立即结束测试。

如果每个 action 必须依次执行,可以使用 for...of 配合 await。同时,调用链上的每一层都要返回或等待 Promise。

推荐写法:依次执行每个 action

文件 1:让 Mocha 等待 Controller

it('Perform Post Payment Actions', async function () {
  await postPaymentController.triggerPostPaymentActions(params);
});

也可以直接返回 Promise:

it('Perform Post Payment Actions', function () {
  return postPaymentController.triggerPostPaymentActions(params);
});

两种写法都能让 Mocha 等待异步操作完成,并捕获其中抛出的错误。

文件 2:使用 for...of,不要使用 forEach

module.exports.triggerPostPaymentActions = async function (params) {
  const actions = params.postAuthActionId.split('-');

  for (const action of actions) {
    const actionAsArray = action
      .split(',')
      .map(value => value.trim());

    switch (actionAsArray[0]) {
      case 'PARTIALREFUND':
        await paymentsPartialRefundApi.submitPaymentsPartialRefundApi(
          actionAsArray,
          params
        );
        break;

      default:
        break;
    }
  }
};

代码会按以下顺序执行:

  1. 解析第一个 action。
  2. 等待第一个 Axios 请求完成。
  3. 执行第二个 action。
  4. 等待第二个 Axios 请求完成。
  5. Controller 返回。
  6. Mocha 结束测试。

示例字符串中的 - 和 , 两侧可能有空格,所以这里调用了 trim()。否则,第二个 action 可能会被解析成 " PARTIALREFUND",无法匹配 case 'PARTIALREFUND'。

文件 3:保留 async/await,并继续向上抛出错误

module.exports.submitPaymentsPartialRefundApi = async function (
  actionDetails,
  params
) {
  const postOptions = {
    url: partialRefundApiUri,
    method: 'POST',
    headers: restHeaders,
    data: params.partialRefundRequest,
  };

  try {
    const response = await axios(postOptions);

    params.partialRefundResponse = response.data;
    process.env.PARTIALREFUNDRESPONSE = JSON.stringify(response.data);

    return response.data;
  } catch (err) {
    process.env.ERRORCODE = String(err);
    process.env.SCRIPT_STATUS = 'failure';
    process.env.PARTIALREFUNDRESPONSE = 'Partial Refund request Failed';

    throw err;
  }
};

throw err 不能省略。它会让 Controller 返回的 Promise 进入失败状态,Mocha 随后会把这项测试标记为失败。

为什么这里不适合使用 forEach

即使把回调声明为 async,外层函数也不会等待回调完成:

actions.forEach(async action => {
  await submitAction(action);
});

forEach() 不会收集或等待回调返回的 Promise。它只会同步遍历所有元素,然后立即返回。

在外层添加 await 也没有用:

await actions.forEach(async action => {
  await submitAction(action);
});

这段代码等待的是 forEach() 的返回值 undefined,而不是内部请求。

如果允许并发执行

如果各个 action 相互独立,不需要等待前一个请求完成,可以使用 Promise.all():

module.exports.triggerPostPaymentActions = async function (params) {
  const actions = params.postAuthActionId.split('-');

  await Promise.all(
    actions.map(async action => {
      const actionAsArray = action
        .split(',')
        .map(value => value.trim());

      switch (actionAsArray[0]) {
        case 'PARTIALREFUND':
          return paymentsPartialRefundApi.submitPaymentsPartialRefundApi(
            actionAsArray,
            params
          );

        default:
          return undefined;
      }
    })
  );
};

不过,当前代码中的多个请求会修改同一组数据:

params.partialRefundResponse
process.env.PARTIALREFUNDRESPONSE

并发执行时,后完成的请求会覆盖先完成请求的结果。如果需要保留每个 action 的响应,应当返回结果数组,不要把结果写入同一个字段。

关于 Mocha timeout

增加 timeout 只能延长 Mocha 等待测试完成的时间,无法修复调用链没有返回或等待 Promise 的问题。只有在异步链已经接通,而 API 本身确实需要更长时间时,才需要调整 timeout:

it('Perform Post Payment Actions', async function () {
  this.timeout(10000);

  await postPaymentController.triggerPostPaymentActions(params);
});

不要同时使用 async 和 Mocha 的 done 回调,选择一种异步模式即可。

从 Axios 请求开始,调用链上的每一层都必须 return 或 await 下一层的 Promise,最后再由 Mocha 返回或等待最外层 Promise。

备注:内容仅供参考。