用Promise封装含进度回调的API,适配async/await及进度条关联
处理带进度回调的Promise封装与ProgressBar关联方案
这个问题我之前也遇到过——因为Promise本身是一次性决议(resolve/reject)的,没法直接处理多次触发的进度回调,所以得稍微调整你的Promise适配层,把进度事件的传递通道留出来。下面给你几个实用的方案:
核心思路
我们需要在封装低阶API时,除了返回Promise用于最终的成功/失败处理,还要提供一个订阅进度更新的入口,这样高层的async/await代码就能同时监听进度,关联到你的ProgressBar组件。
方案1:基础JavaScript封装(带进度订阅)
先模拟一下你的低阶API:
// 模拟低阶API:带成功、失败、进度回调 function lowLevelApi(params, successCb, failureCb, progressCb) { // 模拟异步操作,每隔500ms更新一次进度 let currentProgress = 0; const timer = setInterval(() => { currentProgress += 10; progressCb(currentProgress); // 触发进度回调 if (currentProgress >= 100) { clearInterval(timer); successCb("操作完成"); } }, 500); }
然后修改你的Promise适配层,返回一个包含promise和onProgress订阅方法的对象:
function wrapLowLevelApi(params) { let progressListeners = []; const operationPromise = new Promise((resolve, reject) => { lowLevelApi( params, (result) => resolve(result), (error) => reject(error), (progressValue) => { // 通知所有订阅了进度的回调 progressListeners.forEach(listener => listener(progressValue)); } ); }); return { promise: operationPromise, // 订阅进度,返回取消订阅的方法(避免内存泄漏) onProgress(callback) { progressListeners.push(callback); return () => { progressListeners = progressListeners.filter(l => l !== callback); }; } }; }
高层async/await使用(关联ProgressBar)
在你的业务代码里,就可以同时订阅进度并执行异步操作:
async function runOperation() { const { promise, onProgress } = wrapLowLevelApi({ /* 你的API参数 */ }); // 订阅进度,更新ProgressBar组件 const unsubscribe = onProgress((progress) => { // 这里替换成你的ProgressBar更新逻辑,比如React的setState console.log(`当前进度:${progress}%`); // setProgress(progress); // 示例:React状态更新 }); try { const result = await promise; console.log("操作成功结果:", result); } catch (error) { console.error("操作失败:", error); } finally { // 操作结束后取消订阅,避免内存泄漏 unsubscribe(); } }
方案2:TypeScript类型化封装
如果你的项目用TypeScript,可以给封装层加上清晰的类型定义:
// 定义封装后的返回类型 interface WrappedApiResponse<T> { promise: Promise<T>; onProgress: (callback: (progress: number) => void) => () => void; } // 低阶API的类型声明(根据实际情况调整) type LowLevelApi<T> = ( params: any, successCb: (result: T) => void, failureCb: (error: Error) => void, progressCb: (progress: number) => void ) => void; // 类型化的封装函数 function wrapLowLevelApi<T>(params: any, lowLevelApi: LowLevelApi<T>): WrappedApiResponse<T> { let progressListeners: ((progress: number) => void)[] = []; const promise = new Promise<T>((resolve, reject) => { lowLevelApi( params, (result) => resolve(result), (error) => reject(error), (progress) => progressListeners.forEach(l => l(progress)) ); }); return { promise, onProgress(callback) { progressListeners.push(callback); return () => { progressListeners = progressListeners.filter(l => l !== callback); }; } }; } // 使用示例 async function performTypedOperation() { const { promise, onProgress } = wrapLowLevelApi<string>({ /* 参数 */ }, lowLevelApi); const unsubscribe = onProgress(progress => { console.log(`TypeScript进度:${progress}%`); // 更新ProgressBar组件状态 }); try { const result = await promise; console.log("Typed操作结果:", result); } catch (err) { console.error("Typed操作错误:", err); } finally { unsubscribe(); } }
React组件中实际使用示例
如果是在React组件里,结合状态管理来更新ProgressBar:
import { useState } from 'react'; function FileUploadComponent() { const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const handleUpload = async () => { setIsUploading(true); const { promise, onProgress } = wrapLowLevelApi({ /* 上传参数 */ }); const unsubscribe = onProgress(progress => setUploadProgress(progress)); try { await promise; alert("上传成功!"); } catch (err) { alert(`上传失败:${err.message}`); } finally { unsubscribe(); setIsUploading(false); setUploadProgress(0); } }; return ( <div> <button onClick={handleUpload} disabled={isUploading}> {isUploading ? "上传中..." : "开始上传"} </button> <div className="progress-container" style={{ marginTop: '1rem' }}> <div className="progress-bar" style={{ width: `${uploadProgress}%`, height: '1rem', backgroundColor: '#4CAF50', transition: 'width 0.3s ease' }} /> <p>{uploadProgress}%</p> </div> </div> ); }
关键注意点
- 一定要在操作完成(成功/失败)后取消进度订阅,避免内存泄漏
- 如果你的进度回调传递的不止数字(比如还有状态描述),只需要调整
onProgress的回调参数类型即可 - 这种方式完全兼容你现有的async/await风格,不需要大幅改动高层代码
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

