
本文详解如何将不支持 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 的问题,还能让异步流程具备良好的可观测性、错误传播能力和维护性。