
本文详解如何将不支持 Promise 的回调式异步函数(如 SOAP 客户端的 CreateCredential)正确包装为 Promise,避免返回 undefined,并提供 .then() 链式调用与 async/await 两种现代写法。
本文详解如何将不支持 promise 的回调式异步函数(如 soap 客户端的 `createcredential`)正确包装为 promise,避免返回 `undefined`,并提供 `.then()` 链式调用与 `async/await` 两种现代写法。
在 Node.js 或浏览器环境中,许多遗留库(如 soap)仍采用传统的错误优先回调(Error-First Callback)模式,例如 client.method(params, callback, options)。这类函数本身不返回 Promise,若直接在其后链式调用 .then(),会导致 undefined —— 因为回调函数的执行结果未被 Promise 捕获和转发。
核心解决思路是:手动封装回调函数为 Promise,即使用 new Promise((resolve, reject) => { ... }) 显式桥接回调逻辑。
✅ 正确封装示例
以下是一个通用、可复用的 Promise 封装函数:
function createCredentialWithPromise(credentialClient, rpaReservation, rpaResource, rpaReservationDateFormatted, rpaScheduleGrid, auth) {
return new Promise((resolve, reject) => {
credentialClient.CredentialService.CredentialPort.CreateCredential(
{
Credential: {
CredentialHolderReference: holderReference,
CredentialIdentifier: {
Type: {
Name: 'pt:PIN',
FormatType: 'SIMPLE_NUMBER16'
},
Value: rpaReservation.access_code
},
CredentialAccessProfile: {
AccessProfileToken: rpaResource.access_control_identifier,
ValidFrom: `${rpaReservationDateFormatted} ${rpaScheduleGrid.start_hour}:00`,
ValidTo: `${rpaReservationDateFormatted} ${rpaScheduleGrid.end_hour}:00`
}
}
},
(err, result) => {
if (err) {
console.error('SOAP CreateCredential failed:', err);
reject(err);
} else {
resolve(result);
}
},
{ auth }
);
});
}⚠️ 注意:务必在 reject() 中传入 err(而非空调用),否则后续 .catch() 将无法捕获具体错误信息;同时建议添加日志便于调试。
✅ 推荐用法一:Promise 链式调用(兼容性好)
function _callAccessControl(response, rpaReservation) {
if (rpaResource.access_control_identifier && !accessControlExist) {
return soap.createClientAsync(
'https://ipevia.com/public/files/onvif/credential.wsdl',
{
endpoint: 'https://ipevia.com/index.php?OnvifServer',
forceSoap12Headers: true
}
)
.then(credentialClient =>
createCredentialWithPromise(
credentialClient,
rpaReservation,
rpaResource,
rpaReservationDateFormatted,
rpaScheduleGrid,
auth
)
)
.then(result => {
// 注意:parseInt(result.Token, 10) 前应校验 result.Token 是否存在
if (!result?.Token) throw new Error('Missing Token in SOAP response');
return rpaReservation.update({
access_control_identifier: parseInt(result.Token, 10)
});
});
} else {
return Promise.resolve(rpaReservation); // 统一返回 Promise,保持调用一致性
}
}
// 使用方式(安全可靠)
_callAccessControl(response, rpaReservation)
.then(updatedReservation => {
console.log('Success:', updatedReservation);
})
.catch(err => {
console.error('Failed to configure access control:', err);
});✅ 推荐用法二:async/await(更简洁、可读性强)
async function _callAccessControl(response, rpaReservation) {
if (rpaResource.access_control_identifier && !accessControlExist) {
try {
const credentialClient = await soap.createClientAsync(
'https://ipevia.com/public/files/onvif/credential.wsdl',
{
endpoint: 'https://ipevia.com/index.php?OnvifServer',
forceSoap12Headers: true
}
);
const result = await createCredentialWithPromise(
credentialClient,
rpaReservation,
rpaResource,
rpaReservationDateFormatted,
rpaScheduleGrid,
auth
);
if (!result?.Token) {
throw new Error('Invalid SOAP response: missing Token');
}
return rpaReservation.update({
access_control_identifier: parseInt(result.Token, 10)
});
} catch (err) {
console.error('Access control setup failed:', err);
throw err; // 重新抛出,由上层处理
}
} else {
return rpaReservation;
}
}? 关键注意事项总结
- 始终返回 Promise:即使走同步分支(如 else 分支),也应使用 Promise.resolve(value) 确保返回值类型统一,避免调用方需额外判断。
- 错误必须传递:reject(err) 而非 reject(),确保错误上下文不丢失。
- 参数作用域检查:原代码中 rpaResource、holderReference、rpaScheduleGrid 等变量需确认已在函数作用域内正确定义或传入,否则运行时会报 ReferenceError。
- 空值防护:对 result.Token 等关键字段做存在性校验,防止 parseInt(undefined, 10) 返回 NaN 导致数据异常。
- 避免混合风格:不要在 async 函数中混用 .then() 和 await(除非有明确理由),保持逻辑清晰。
通过以上封装与重构,你不仅能彻底解决 result is undefined 的问题,还能让异步流程具备良好的可观测性、错误传播能力和维护性。










