You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:46:37