在现代Web开发中,异步编程已成为JavaScript的核心概念。当处理耗时操作时,如文件上传、数据处理或API调用,向用户提供进度反馈至关重要。本文将探讨如何在JavaScript中实现进度通知的异步模式。
回调函数与进度通知
传统的回调函数模式也可以实现进度通知,尽管这种方式在现代开发中已不太推荐:
javascript
function processData(data, callback, progressCallback) {
let total = data.length;
let processed = 0;
data.forEach((item, index) => {
// 模拟处理每个项目的耗时
setTimeout(() => {
processItem(item);
processed++;
// 调用进度回调
progressCallback(processed / total);
if (processed === total) {
callback('处理完成');
}
}, index * 100);
});
}
// 使用示例
processData(
[1, 2, 3, 4, 5],
result => console.log(result),
progress => console.log(`进度: ${Math.round(progress * 100)}%`)
);
Promise与进度通知
Promise本身不直接支持进度通知,但我们可以通过扩展来实现:
javascript
class ProgressPromise {
constructor(executor) {
this._progressHandlers = [];
this._promise = new Promise((resolve, reject) => {
executor(resolve, reject, progress => {
this._progressHandlers.forEach(handler => handler(progress));
});
});
}
then(onFulfilled, onRejected) {
return this._promise.then(onFulfilled, onRejected);
}
catch(onRejected) {
return this._promise.catch(onRejected);
}
progress(handler) {
this._progressHandlers.push(handler);
return this;
}
}
// 使用示例
function asyncTask() {
return new ProgressPromise((resolve, reject, progress) => {
let percent = 0;
const interval = setInterval(() => {
percent += 10;
progress(percent);
if (percent >= 100) {
clearInterval(interval);
resolve('完成');
}
}, 500);
});
}
asyncTask()
.progress(p => console.log(`进度: ${p}%`))
.then(result => console.log(result));
使用Async/Await与事件发射器
结合async/await和事件发射器可以实现更清晰的进度通知:
javascript
const EventEmitter = require('events');
class AsyncProcessor extends EventEmitter {
async process(data) {
const total = data.length;
let processed = 0;
for (const item of data) {
await this.processItem(item);
processed++;
this.emit('progress', processed / total);
}
return '处理完成';
}
processItem(item) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`处理项目: ${item}`);
resolve();
}, 500);
});
}
}
// 使用示例
const processor = new AsyncProcessor();
processor.on('progress', progress => {
console.log(`进度: ${Math.round(progress * 100)}%`);
});
processor.process([1, 2, 3, 4, 5])
.then(result => console.log(result));
现代浏览器API中的进度通知
许多现代浏览器API原生支持进度通知:
javascript
// Fetch API上传文件带进度
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`上传进度: ${percent}%`);
}
};
return new Promise((resolve, reject) => {
xhr.onload = () => resolve(xhr.response);
xhr.onerror = () => reject(new Error('上传失败'));
xhr.open('POST', '/upload', true);
xhr.send(formData);
});
}
// 或者使用fetch + ReadableStream
async function processStreamWithProgress(response) {
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
let chunks = [];
while(true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
console.log(`接收进度: ${Math.round((receivedLength / contentLength) * 100)}%`);
}
return new Blob(chunks);
}
最佳实践
- 适度更新频率:进度更新不应太频繁,以免影响性能
- 平滑显示:考虑使用动画或节流来平滑进度显示
- 错误处理:确保在操作失败时提供适当的反馈
- 取消支持:为长时间运行的操作提供取消功能
- 预估时间:在可能的情况下,提供剩余时间估计
通过合理运用这些技术,你可以为用户提供流畅的异步操作体验,显著提升应用的用户友好性。