【问题标题】:How to return subscription and promise from single function in angular?如何从角度的单个函数返回订阅和承诺?
【发布时间】:2020-10-23 06:33:50
【问题描述】:

我正在开发一个实现文件上传功能的 Angular 项目。 我创建了一个上传服务,并在其中创建了一个用于上传文件的函数

service.ts :

upload(file: File){
  return new Promise(resolve=>{
    var file = file;
    bucket.upload(file).on("httpUplaodprogress", (progress)=>{
        const percent = progress.percent

        -->  Here I want to emit this percent value from this function. and want to get it where it is from called.


    }).send(response =>{
      response.error ? reject(response.err);
      resolve(response);
    })
  })
}

现在很多地方都在使用这个上传服务。

例如:

uploadComponent.ts :

progress:number = 0;

constructor(private uploadService: UploadService) {}

handleFileInput(file){
    this.uploadService.upload(file).then(result=>{
       result ? show success msg for file uplaod success : show error

       ==> I want to subscribe service's upload function's progress variable 
           here for assign that value into this host component's progress variable which is 
           bind with progressbar copmponent.
   
    })
}

uploadComponent.html

<progressbar [progress]="progress" />

这是当前场景,我们无法将进度变量从服务函数访问到调用服务函数的主机组件。

一种方式是,我们可以将订阅服务功能获取到主机组件中,并且可以不断获取进度值。但是如何?

因为服务功能已经返回承诺。如何在 Promise 中使用订阅并将其订阅到主机组件中。

我还考虑过将全局订阅变量加入服务并将其订阅到主机组件中。但是从多个地方上传文件并显示进度条时可能会导致问题。

我想为每次调用保留订阅变量的本地范围。

如果有人知道它,如何从 Promise 函数广播值并在调用它的主机函数中接收..然后请回复。这对我很有帮助。

【问题讨论】:

    标签: angular promise observable subscription


    【解决方案1】:

    你应该从你的服务中返回一个 Observable 而不是一个 Promise。

    UploadService

    // note that BucketUploadResponse is an interface I made up
    upload(file: File): Promise<BucketUploadResponse> {
         // delegate to our Observable implementation and convert to promise
         return this.upload$(file).toPromise();
    }
    
    // note that BucketUploadResponse is an interface I made up
    upload$(file: File): Observable<number | BucketUploadResponse> {
            return new Observable<number>((subscriber) => {
                bucket
                    .upload(file)
                    .on('httpUplaodprogress', (progress) => {
                        const percent = progress.percent;
                        // emit progress
                        subscriber.next(percent);
                    })
                    .send((response) => {
                        if (response.error) {
                            // error out
                            subscriber.error(response.error);
                        } else {
                            // emit response as last item to keep old contract
                            subscriber.next(response);
                            subscriber.complete();
                        }
                    });
    
                return () => {
                    // called when no subscription is left. You could abort the upload here for example
                };
            });
        }
    

    在消费组件中,可以订阅 Observable:

        progress:number = 0;
        
        private uploadSubscription?: Subscription;
    
        constructor(private uploadService: UploadService) {}
    
        handleFileInput(file){
            this.progress = 0;
            // unsubscribe from an existing upload
            this.uploadSubscription?.unsubscribe();
            this.uploadSubscription = this.uploadService.upload(file).subscribe((progress) => {
                if (typeof progress === 'number') {
                    this.progress = progress;
                } else {
                    // handle BucketUploadResponse if required
                }
            }, () => {
                // complete, show success message
            }, (error) => {
                // error, show error message
            })
        }
    

    【讨论】:

    • 这是最好的解决方案...但是我的项目太大并且在很多地方都使用了上传服务,在所有地方我都需要更新代码以从服务中获取结果。是否有其他可用的解决方案,例如本地订阅实例或订阅数组……然后在此处提及……
    • 如何向UploadService 添加第二个方法,该方法简单地委托给Observable 实现并返回Observable .toPromise()?我将更新上面的代码示例。
    • 请注意,从 RxJS 7 开始,.toPromise() 将被弃用:morioh.com/p/3653be9c0c51 当更新到 RxJS 7 时,请改用 lastValueFrom
    【解决方案2】:

    您可以将标头传递给您的 http 调用以订阅进度

    reportProgress: true,
    observe: 'events'
    

    在你上传组件

    yourservice.subscribe((e: HttpEvent<any>) => {
              switch (e.type) {
                case HttpEventType.Sent:
                  break;
                case HttpEventType.ResponseHeader:
                  break;
                case HttpEventType.UploadProgress:
                  this.progress = Math.round(e.loaded / e.total * 100);
                  break;
                case HttpEventType.Response:
                  this.progress = -1;
              }
        });
    

    如果您愿意,您可以选择实施行为主体以实现此目的 在您的 service.ts 中将 processPercentage 声明为行为主题

    const percent = progress.percent;
    this.processPercentage.next(percent);
    

    在您的 uploadComponent.ts 中

    subscription:Subscription;
    constructor(private uploadService: UploadService,private service:Service) {}
    ngOnInit(){
     this.getProgress();
    }
    
    getProgress(){
     this.subscription = 
     this.service.processPercentage.asObservable().susbcribe((progress) => {
      this.progress = progress;
      console.log(this.progress);
      // You can bind to your progressbar from here 
     });
    }
    
    ngOnDestroy(){
     this.subscription.unsubscribe();
    }
    

    【讨论】:

    • 我已经考虑过服务内部的全局订阅对象。但是在多个地方使用 service.upload() 时可能会导致问题。进度值将覆盖并在订阅的两个地方显示相同的进度..
    猜你喜欢
    • 2020-04-09
    • 1970-01-01
    • 2018-11-30
    • 2021-12-09
    • 2017-01-10
    • 2019-11-12
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多