Mocha + Node.js + Axios 中 async/await 未等待请求
结论
await 只会暂停当前 async 函数,不会自动让上层调用者等待。
这段代码的异步调用链断在了两个地方:
- Controller 调用了
submitPaymentsPartialRefundApi(),却没有使用await,也没有返回它的 Promise。 - Mocha 的
itblock 既没有返回 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;
}
}
};
代码会按以下顺序执行:
- 解析第一个 action。
- 等待第一个 Axios 请求完成。
- 执行第二个 action。
- 等待第二个 Axios 请求完成。
- Controller 返回。
- 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。
备注:内容仅供参考。
