简体   繁体   English

如何手动完成angular http observable

[英]How to manually complete the angular http observable

I am developing multiple parallel file upload feature with ability to delete/cancel ongoing http calls.我正在开发多个并行文件上传功能,能够删除/取消正在进行的 http 调用。 Once all call gets completed/cancelled, I notify the consumer about it.一旦所有呼叫完成/取消,我就会通知消费者。

For this I am combining individual http observables using forkJoin .为此,我使用forkJoin组合各个 http observables。 But in case user clicks on cancel button, I should not be waiting for the completion of actual http response.但是如果用户点击取消按钮,我不应该等待实际 http 响应的完成。

takeUntill won't handle it elegantly as it will act only after receiving the next value from underlying source http stream. takeUntill 不会优雅地处理它,因为它只有在从底层源 http 流接收下一个值后才会起作用。

this.uploadFiles().subscribe(data => {
  console.warn("Upload completed now!!!!!", data);
});

uploadFiles() {
  return forkJoin(
    this.files.map(file => // returns array of observable
        this.uploadFile(file).pipe( catchError(() => of("Error re-emitted as success to prevent early exit"))))
  ).pipe(map(() => {
       // logic for some user friendly statistic
      return data;
    }));
}

Use takeUntil with a Subject as notifier to complete Observables.使用takeUntil和一个 Subject 作为通知程序来完成 Observables。 You can pass a file id to the Subject and use filter in takeUntil to only cancel the file upload of a file with a given id.您可以将文件 id 传递给 Subject 并在takeUntil使用filter来仅取消具有给定 id 的文件的文件上传。

Use defaultIfEmpty to provide a value that indicates a cancelled request.使用defaultIfEmpty提供一个指示已取消请求的值。 This also prevents the outer forkJoin from completing immediately when an inner empty request completes.这也可以防止外部forkJoin在内部空请求完成时立即完成。

private cancelUpload$ = new Subject<number>();

uploadFiles() {
  let errorCount = 0, cancelledCount = 0, successCount = 0;
  return forkJoin(this.dummyFiles.map(file =>
    this.uploadFile(file).pipe(
      // react to CANCEL event
      map(response => response == 'CANCEL' ? ++cancelledCount : ++successCount),
      catchError(() => of(++errorCount))
    )
  )).pipe(map(() => ({ errorCount, successCount, cancelledCount })));
}

uploadFile(file: any) {
  http$.pipe(
    ...
    takeUntil(this.cancelUpload$.pipe(filter(id => id == file.id))), // cancel
    defaultIfEmpty('CANCEL'), // provide value when cancelled
  )
}

cancelUpload(file: any) {
  file.uploadStatus = "cancelled";
  this.cancelUpload$.next(file.id) // cancel action
}

https://stackblitz.com/edit/angular-zteeql-e1zacp https://stackblitz.com/edit/angular-zteeql-e1zacp

Assign the subscription to the variable and cancel it with button:将订阅分配给变量并使用按钮取消它:

$http: Subscription;
this.$http = this.http.post(this.uploadUrl, formData, {
      reportProgress: false
      // observe: 'events',
});

And in the cancel function:在取消功能中:

cancelUpload(file: any) {
  file.uploadStatus = "cancelled"; 
  this.$http.unsubscribe();
}

Apart from takeUntil we can also use merge or race operator to handle this scenario.除了takeUntil我们还可以使用mergerace运算符来处理这种情况。 However it doesn't change the underlying logic.然而,它并没有改变底层逻辑。

Step 1: Create a Subject for individual upload第 1 步:为个人上传创建Subject

file.cancelUpload$ = new Subject();

Step 2: Merge this subject with actual http call第 2 步:将此主题与实际的 http 调用合并

merge will complete the stream if any of the observable emits the error.如果任何 observable 发出错误,merge 将完成流。 ie when we emit error from cancelUpload$ subject, http request will get cancelled automatically (look into network tab).即,当我们从cancelUpload$主题发出错误时,http 请求将自动取消(查看网络选项卡)。

return merge(file.cancelUpload$, $http.pipe(...

Step 3: Actual Cancellation Code第 3 步:实际取消代码

cancelUpload(file: any) {
  file.uploadStatus = "cancelled";
  file.cancelUpload$.error(file.uploadStatus);// implicitly subject gets completed
}

Step 4: Complete the cancelUpload$ Subject in case of upload error/success第 4 步:在上传错误/成功的情况下完成cancelUpload$主题

It will ensure that merge operation will gets completed as both stream is now complete.它将确保merge操作将完成,因为两个流现在都已完成。 And therefore forkJoin will receive the response.因此forkJoin将收到响应。

Refer https://stackblitz.com/edit/angular-zteeql?file=src%2Fapp%2Fhttp-example.ts参考https://stackblitz.com/edit/angular-zteeql?file=src%2Fapp%2Fhttp-example.ts

  uploadFiles() {
    let errorCount = 0,cancelledCount = 0, successCount = 0;
    return forkJoin(
      this.dummyFiles
        .map(file =>
          this.uploadFile(file).pipe(
            catchError(() => of("Error re-emitted as success")) // value doesn't matter
          )
        )
    ).pipe(
      map(() => { // map would receive array of files in the order it was subscribed
        this.dummyFiles.forEach(file => {
          switch (file.uploadStatus) {
            case "success": successCount++; break;
            case "cancelled": cancelledCount++; break;
            case "error": errorCount++; break;
          }
        });
        return { errorCount, successCount, cancelledCount };
      })
    );
  }

  uploadFile(file: any) {
    const formData = new FormData();
    const binaryContent = new Blob([Array(1000).join("some random text")], {
      type: "text/plain"
    }); // dummy data to upload
    formData.append("file", binaryContent);
    const $http = this.http.post(this.uploadUrl, formData, {
      reportProgress: false
      // observe: 'events',
      // withCredentials: true
    });
    file.cancelUpload$ = new Subject();
    file.uploadStatus = "inProgress";
    return merge(
      file.cancelUpload$,
      $http.pipe(
        tap(data => {
          file.uploadStatus = "uploaded";
          file.cancelUpload$.complete();
        }),
        catchError(event => {
          file.uploadStatus = "error";
          file.cancelUpload$.complete();
          return throwError("error");
        })
      )
    );
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM