
本文详解如何将不支持 Promise 的回调式异步函数(如 SOAP 客户端调用)安全封装为 Promise,避免返回 undefined,并提供 .then() 链式调用与 async/await 两种现代写法。
本文详解如何将不支持 promise 的回调式异步函数(如 soap 客户端调用)安全封装为 promise,避免返回 `undefined`,并提供 `.then()` 链式调用与 `async/await` 两种现代写法。
在 Node.js 或浏览器环境中,许多传统库(如 soap)仍采用「错误优先回调(error-first callback)」模式,例如 CreateCredential(..., callback, options)。这类函数本身不返回 Promise,若直接在 .then() 链中调用却不显式返回值,后续 .then() 将接收到 undefined —— 这正是原代码中 result is undefined 的根本原因。
要解决该问题,核心原则是:所有异步操作必须被统一纳入 Promise 执行流,并确保每一步都显式 return。以下是推荐的实践方案:
✅ 正确封装回调函数为 Promise
使用 new Promise() 包装回调函数,显式处理成功与失败分支:
function createCredentialWithPromise(credentialClient, rpaReservation, rpaResource, rpaReservationDateFormatted, rpaScheduleGrid) {
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); // 注意:此处 resolve(result),而非 resolve()
}
},
{ auth }
);
});
}⚠️ 注意事项:
- reject() 和 resolve() 必须被调用,且仅调用一次;
- 错误对象应完整传递(如 reject(err)),便于上层捕获堆栈与上下文;
- 不要忽略 err 参数,否则异常将静默丢失。
✅ 方案一:链式 .then() 写法(兼容性好)
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
)
)
.then(result => {
// result 是 CreateCredential 的响应体(含 Token)
const token = parseInt(result.Token, 10);
return rpaReservation.update({
access_control_identifier: token
});
});
} else {
return Promise.resolve(rpaReservation); // 统一返回 Promise,保持调用一致性
}
}
// 调用方式(可正常获取最终更新后的 reservation)
_callAccessControl(response, rpaReservation)
.then(updatedReservation => {
console.log('✅ 更新成功:', updatedReservation);
})
.catch(err => {
console.error('❌ 执行失败:', 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
);
const token = parseInt(result.Token, 10);
return await rpaReservation.update({
access_control_identifier: token
});
} catch (err) {
console.error('SOAP 流程中断:', err);
throw err; // 保持错误冒泡,由调用方处理
}
} else {
return rpaReservation;
}
}? 总结要点
- ❌ 错误模式:在回调中执行 .update() 但未 return 其 Promise → 链断裂 → undefined;
- ✅ 正确模式:每个异步步骤必须 return 一个 Promise,形成连续的数据流;
- ? 封装是关键:将任意回调函数转为 Promise 是标准化、可复用的基础能力;
- ?️ 始终 catch 或 try/catch:防止未处理的 Promise rejection 导致进程崩溃;
- ? 推荐优先使用 async/await:逻辑线性、调试友好、错误处理直观。
遵循以上模式,即可稳健地将遗留回调 API 无缝集成到现代 Promise/async 生态中。








