Node.js Async/Await异步函数未完成即返回值问题排查
我完全搞不懂自己在Async/Await概念上哪里出错了。以下是我的Node.js代码(分两个文件):
在execute.js中,console.log("hasInvoiceAlreadyBeenPaid:", hasInvoiceAlreadyBeenPaid);语句在controller.checkIfAuthNetChargedInvoice()执行完成前就运行了,输出的hasInvoiceAlreadyBeenPaid值为undefined。
我明明把函数定义为Async函数且使用了Await,对此十分困惑。需要说明的是,checkIfAuthNetChargedInvoice函数最终能成功运行,但日志中console.log("🎨 ~ ctrl:", ctrl);刚输出,紧接着就出现hasInvoiceAlreadyBeenPaid: undefined,之后该函数剩余逻辑才完成执行。我知道这是对async/await的理解误区,但到底哪里错了?
代码示例
execute.js
const controller = require("./controller"); const hasInvoiceAlreadyBeenPaid = await controller.checkIfAuthNetChargedInvoice( lastUnpaidSubscriptionInvoice, authorizeNetProfileId, ); console.log("hasInvoiceAlreadyBeenPaid:", hasInvoiceAlreadyBeenPaid); if (hasInvoiceAlreadyBeenPaid) { console.log("🎉 INVOICE ALREADY PAID! "); }
controller.js
exports.checkIfAuthNetChargedInvoice = async (invoice, customerProfileId) => { try { const invoiceString = invoice._id.toString(); const authNetInvoiceNumber = invoiceString.slice(-12); var merchantAuthenticationType = new ApiContracts.MerchantAuthenticationType(); merchantAuthenticationType.setName(authorizeNetAPILoginKey); merchantAuthenticationType.setTransactionKey(authorizeNetTransactionKey); var paging = new ApiContracts.Paging(); paging.setLimit(100); paging.setOffset(1); var sorting = new ApiContracts.TransactionListSorting(); sorting.setOrderBy(ApiContracts.TransactionListOrderFieldEnum.ID); sorting.setOrderDescending(true); var getRequest = new ApiContracts.GetTransactionListForCustomerRequest(); getRequest.setMerchantAuthentication(merchantAuthenticationType); getRequest.setCustomerProfileId(customerProfileId); getRequest.setPaging(paging); getRequest.setSorting(sorting); var ctrl = new ApiControllers.GetTransactionListForCustomerController( getRequest.getJSON(), ); console.log("🎨 ~ ctrl:", ctrl); ctrl.execute(async function () { var apiResponse = ctrl.getResponse(); console.log("🎨 ~ apiResponse:", apiResponse); var response = new ApiContracts.GetTransactionListResponse(apiResponse); console.log(JSON.stringify(response, null, 2)); if (response != null) { if ( response.getMessages().getResultCode() == ApiContracts.MessageTypeEnum.OK ) { console.log( "Message Code : " + response.getMessages().getMessage()[0].getCode(), ); console.log( "Message Text : " + response.getMessages().getMessage()[0].getText(), ); if (response.getTransactions() != null) { var transactions = response.getTransactions().getTransaction(); for (var i = 0; i < transactions.length; i++) { const transaction = transactions[i]; const transactionId = transaction.getTransId(); const transactionStatus = transaction.getTransactionStatus(); const settleAmount = transaction.getSettleAmount(); if ( transaction.invoiceNumber == authNetInvoiceNumber && transactionStatus === "settledSuccessfully" ) { invoice.status = "paid"; await invoice.save(); return true; } } } } else { const newErrorLog = new ErrorLog({ error }); await newErrorLog.save(); } } else { let errorText = "checkIfAuthNetChargedInvoice: Null Response "; if (invoice._id) { errorText = "checkIfAuthNetChargedInvoice: Null Response for Invoice: " + invoice._id; } const newErrorLog = new ErrorLog({ error: errorText }); await newErrorLog.save(); } }); } catch (error) { console.log("error", error); const newErrorLog = new ErrorLog({ error }); await newErrorLog.save(); } };
核心问题
你给函数加了async关键字,但内部的ctrl.execute()是基于回调的异步操作,外层的async函数并没有等待这个回调执行完成就直接返回了——因为execute()不会返回Promise,外层的await根本等不到异步逻辑结束,函数默认返回undefined,所以才会出现日志顺序混乱、结果为undefined的情况。
另外,回调里的return true只是回调函数的返回值,不会传递给外层的checkIfAuthNetChargedInvoice函数。
修复步骤
要解决这个问题,需要把ctrl.execute()的回调逻辑包装成Promise,让async函数能等待这个异步操作完成后再返回结果:
修改后的controller.js代码
exports.checkIfAuthNetChargedInvoice = async (invoice, customerProfileId) => { try { const invoiceString = invoice._id.toString(); const authNetInvoiceNumber = invoiceString.slice(-12); var merchantAuthenticationType = new ApiContracts.MerchantAuthenticationType(); merchantAuthenticationType.setName(authorizeNetAPILoginKey); merchantAuthenticationType.setTransactionKey(authorizeNetTransactionKey); var paging = new ApiContracts.Paging(); paging.setLimit(100); paging.setOffset(1); var sorting = new ApiContracts.TransactionListSorting(); sorting.setOrderBy(ApiContracts.TransactionListOrderFieldEnum.ID); sorting.setOrderDescending(true); var getRequest = new ApiContracts.GetTransactionListForCustomerRequest(); getRequest.setMerchantAuthentication(merchantAuthenticationType); getRequest.setCustomerProfileId(customerProfileId); getRequest.setPaging(paging); getRequest.setSorting(sorting); var ctrl = new ApiControllers.GetTransactionListForCustomerController( getRequest.getJSON(), ); console.log("🎨 ~ ctrl:", ctrl); // 用Promise包裹回调逻辑 return new Promise(async (resolve, reject) => { ctrl.execute(async function () { try { var apiResponse = ctrl.getResponse(); console.log("🎨 ~ apiResponse:", apiResponse); var response = new ApiContracts.GetTransactionListResponse(apiResponse); console.log(JSON.stringify(response, null, 2)); if (response != null) { if ( response.getMessages().getResultCode() == ApiContracts.MessageTypeEnum.OK ) { console.log( "Message Code : " + response.getMessages().getMessage()[0].getCode(), ); console.log( "Message Text : " + response.getMessages().getMessage()[0].getText(), ); if (response.getTransactions() != null) { var transactions = response.getTransactions().getTransaction(); for (var i = 0; i < transactions.length; i++) { const transaction = transactions[i]; const transactionId = transaction.getTransId(); const transactionStatus = transaction.getTransactionStatus(); const settleAmount = transaction.getSettleAmount(); if ( transaction.invoiceNumber == authNetInvoiceNumber && transactionStatus === "settledSuccessfully" ) { invoice.status = "paid"; await invoice.save(); // 找到匹配交易,返回true resolve(true); return; } } } // 遍历完未找到匹配交易,返回false resolve(false); } else { const newErrorLog = new ErrorLog({ error }); await newErrorLog.save(); resolve(false); } } else { let errorText = "checkIfAuthNetChargedInvoice: Null Response "; if (invoice._id) { errorText = "checkIfAuthNetChargedInvoice: Null Response for Invoice: " + invoice._id; } const newErrorLog = new ErrorLog({ error: errorText }); await newErrorLog.save(); resolve(false); } } catch (callbackError) { // 处理回调内部的异步错误 console.log("callback error", callbackError); const newErrorLog = new ErrorLog({ error: callbackError }); await newErrorLog.save(); reject(callbackError); } }); }); } catch (error) { console.log("error", error); const newErrorLog = new ErrorLog({ error }); await newErrorLog.save(); return false; } };
关键修改点
- 用
new Promise()包裹ctrl.execute()的回调逻辑,通过resolve()传递最终结果,reject()处理错误 - 把回调里的
return true替换为resolve(true),确保结果能传递到外层async函数 - 遍历完交易未找到匹配项时,手动
resolve(false),避免Promise一直处于pending状态 - 给回调内部添加try/catch,处理
invoice.save()这类异步操作的错误,避免静默失败
修改后,外层的await就能正确等待整个异步逻辑完成,hasInvoiceAlreadyBeenPaid会拿到正确的布尔值,日志顺序也会符合预期。
内容的提问来源于stack exchange,提问作者personwholikestocode

