如何在NGRX Effect中取消Angular文件上传的待处理REST请求?
这个场景我太熟了!在NGRX Effect里实现上传请求的中止,确实不能像组件里那样直接抓个subscription来unsubscribe,得结合RxJS操作符和NGRX的Action机制来搞。给你两个实用的方案,按需选:
方案1:单上传任务的全局取消
如果你的页面同时只会处理一个上传任务,用这个方案最省心:
第一步:新增取消Action
先定义一个触发取消操作的Action:
export enum ActionTypes { UploadFile = '[Upload] Start Upload', CancelUpload = '[Upload] Cancel Upload', UpdateUploadProgress = '[Upload] Update Progress', UploadFileSuccess = '[Upload] Upload Success', UploadFileFailure = '[Upload] Upload Failure' } export class CancelUploadAction implements Action { readonly type = ActionTypes.CancelUpload; }
第二步:修改Effect逻辑
用RxJS的takeUntil操作符监听取消Action,一旦取消Action被分发,就自动终止上传请求的Observable(Angular HttpClient的请求会随之取消):
@Effect({ dispatch: false }) uploadFile = this.actions .ofType(ActionTypes.UploadFile) .map(toPayload) .switchMap(file => this.service.upload(file).pipe( map(event => { if (event.type === HttpEventType.UploadProgress) { const percentDone = Math.round(100 * event.loaded / event.total); this.store.dispatch(new UpdateUploadProgressAction(percentDone)); } else if (event instanceof HttpResponse) { if (event.status >= 400) { console.error(event); this.store.dispatch(new UploadFileFailureAction(event)); } else { this.store.dispatch(new UploadFileSuccessAction(event.body)); } } }), // 监听取消Action,触发后终止上传 takeUntil(this.actions.ofType(ActionTypes.CancelUpload)), // 不管上传成功、失败还是取消,都做收尾清理 finalize(() => { this.store.dispatch(new UpdateUploadProgressAction(0)); }) ) );
第三步:在组件里触发取消
用户点击“取消”按钮时,直接分发CancelUploadAction就行:
onCancelUpload() { this.store.dispatch(new CancelUploadAction()); }
方案2:多上传任务的独立取消
如果页面支持同时上传多个文件,需要给每个任务分配唯一ID,确保取消操作只影响目标任务:
第一步:修改Action带任务ID
给上传和取消Action都加上唯一的uploadId:
export class UploadFileAction implements Action { readonly type = ActionTypes.UploadFile; constructor(public payload: { file: File; uploadId: string }) {} } export class CancelUploadAction implements Action { readonly type = ActionTypes.CancelUpload; constructor(public payload: { uploadId: string }) {} } // 进度更新Action也要带上ID,方便组件区分任务 export class UpdateUploadProgressAction implements Action { readonly type = ActionTypes.UpdateUploadProgress; constructor(public payload: { percentDone: number; uploadId: string }) {} }
第二步:调整Effect过滤目标任务
在takeUntil里过滤出对应ID的取消Action,确保只终止指定的上传:
@Effect({ dispatch: false }) uploadFile = this.actions .ofType(ActionTypes.UploadFile) .map(toPayload) .switchMap(({ file, uploadId }) => this.service.upload(file).pipe( map(event => { if (event.type === HttpEventType.UploadProgress) { const percentDone = Math.round(100 * event.loaded / event.total); this.store.dispatch(new UpdateUploadProgressAction({ percentDone, uploadId })); } else if (event instanceof HttpResponse) { if (event.status >= 400) { console.error(event); this.store.dispatch(new UploadFileFailureAction({ response: event, uploadId })); } else { this.store.dispatch(new UploadFileSuccessAction({ body: event.body, uploadId })); } } }), // 只监听当前任务ID的取消Action takeUntil( this.actions .ofType(ActionTypes.CancelUpload) .filter(action => action.payload.uploadId === uploadId) ), finalize(() => { this.store.dispatch(new UpdateUploadProgressAction({ percentDone: 0, uploadId })); }) ) );
几个最佳实践
- 永远用RxJS操作符管理订阅,避免在Effect里手动调用
unsubscribe,NGRX会自动管理Effect的生命周期,手动取消容易导致状态不一致。 - 用
finalize做收尾工作,比如重置进度、清理临时状态,不管上传是成功、失败还是被取消,都能保证UI状态正确。 - 如果上传任务需要持久化(比如刷新页面后继续),可以把任务ID和状态存在NGRX Store里,恢复页面时重新触发未完成的上传。
内容的提问来源于stack exchange,提问作者kauppfbi
相关产品推荐
相关产品推荐

