如何将基于回调的函数封装为 Promise 并正确返回结果

2次阅读

如何将基于回调的函数封装为 Promise 并正确返回结果

本文详解如何将不支持 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 生态中。

text=ZqhQzanResources