如何将基于回调的异步方法封装为 Promise

2次阅读

如何将基于回调的异步方法封装为 Promise

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

text=ZqhQzanResources