如何在JavaScript中用Promise实现上传状态更新?
问题分析与解决方案
咱们先搞定你当下遇到的回调参数异常问题,再聊聊Promise有没有进度监听的替代方案:
一、为什么回调里的progress是undefined?
看你代码里调用回调的这行:
callbackProgress.call(percentage, id)
这里踩了call方法的使用坑:call的第一个参数是指定回调执行时的this上下文,从第二个参数开始才是传给回调的实际参数。你把percentage放在了第一个位置,导致回调里的progress拿到的是this(非严格模式下会被转成包装对象,严格模式直接是undefined),而id才是第一个真实传入的参数——这就是为啥只有id正常,progress是undefined的原因!
修复方法很简单,二选一就行:
- 如果你不需要指定回调的
this,直接调用就行:
callbackProgress(percentage, id)
- 如果确实要指定
this,把上下文(比如null或者你需要的对象)放在第一个参数位置:
callbackProgress.call(null, percentage, id)
二、Promise有没有onProgressChange这类方法?
很遗憾,原生Promise本身没有内置的进度监听方法——因为Promise的设计初衷是代表「单一异步结果」(要么成功resolve,要么失败reject),它不支持多次状态更新。不过咱们有几种替代方案实现进度上报:
1. 继续用回调(最省心的方案)
修复刚才的调用问题后,这个方案就能正常工作,也是日常开发中最常用的进度上报方式,不需要额外折腾。
2. 封装自定义的带进度Promise类
如果你想脱离回调的写法,可以自己封装一个扩展原生Promise的类,给它加进度监听能力:
class ProgressPromise extends Promise { constructor(executor) { super((resolve, reject) => { // 给executor多传一个更新进度的方法 return executor(resolve, reject, (progress, id) => { if (this.onProgressHandler) { this.onProgressHandler(progress, id); } }); }); } // 定义进度监听方法 onProgress(callback) { this.onProgressHandler = callback; return this; } } // 改造你的uploadImages函数 export const uploadImages = (images) => { return new ProgressPromise((resolve, reject, updateProgress) => { // 原来的上传逻辑里,需要上报进度时调用updateProgress(percentage, id) const myUploadProgress = (thumbId) => (progress) => { let id = thumbId; let percentage = Math.floor((progress.loaded * 100) / progress.total); updateProgress(percentage, id); // 用这个方法更新进度 }; // ...剩下的上传逻辑和之前一样 var originals = [] var thumbnails = [] let totalNrOfImages = images.length var uploadImageCounter = 0 var i; for (i = 0; i < images.length; i++) { var thumbId = i var config = { onUploadProgress: myUploadProgress(thumbId) }; const formData = new FormData() formData.append('img', images[i]) uploadImage2(formData, config).then(resp => { originals.push(resp.oriUrl) thumbnails.push(resp.thumbUrl) uploadImageCounter=uploadImageCounter+1 if(images.length == uploadImageCounter){ resolve({orig:originals, thumbs:thumbnails}); } }) } }); }; // 调用时就可以链式监听进度了 uploadImages(fileList) .onProgress((progress, id) => { console.log(`SCENE1 | uploadImages() | id: ${id}, progress: ${progress}%`); }) .then(response => { console.log("SCENE1 | uploadImages() | done, response: ", response); }) .catch(error => { console.log("SCENE1 | uploadImages() | error!"); });
3. 用事件发射器传递进度
如果你的项目里有用事件相关的能力(浏览器的EventTarget或者Node.js的EventEmitter),也可以用事件来传递进度:
export const uploadImages = (images) => { const progressEmitter = new EventTarget(); const promise = new Promise((resolve, reject) => { const myUploadProgress = (thumbId) => (progress) => { let id = thumbId; let percentage = Math.floor((progress.loaded * 100) / progress.total); // 触发进度事件 progressEmitter.dispatchEvent(new CustomEvent('upload-progress', { detail: { progress, id } })); }; // ...剩下的上传逻辑和之前一样 var originals = [] var thumbnails = [] let totalNrOfImages = images.length var uploadImageCounter = 0 var i; for (i = 0; i < images.length; i++) { var thumbId = i var config = { onUploadProgress: myUploadProgress(thumbId) }; const formData = new FormData() formData.append('img', images[i]) uploadImage2(formData, config).then(resp => { originals.push(resp.oriUrl) thumbnails.push(resp.thumbUrl) uploadImageCounter=uploadImageCounter+1 if(images.length == uploadImageCounter){ resolve({orig:originals, thumbs:thumbnails}); } }) } }); return { promise, progressEmitter }; }; // 调用方式 const { promise, progressEmitter } = uploadImages(fileList); progressEmitter.addEventListener('upload-progress', (e) => { const { progress, id } = e.detail; console.log(`SCENE1 | uploadImages() | id: ${id}, progress: ${progress}%`); }); promise.then(response => { console.log("SCENE1 | uploadImages() | done, response: ", response); }).catch(error => { console.log("SCENE1 | uploadImages() | error!"); });
总结
先把回调的调用方式改对,这是解决当前progress为undefined的核心;如果想优化写法,根据自己的项目场景选自定义Promise类或者事件的方式就行。
内容的提问来源于stack exchange,提问作者KasparTr
相关产品推荐
相关产品推荐

