
本文详解如何将不支持 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 } ); }); }
⚠️ 注意事项:
✅ 方案一:链式 .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 生态中。