只运行一次订阅

Posted

技术标签:

【中文标题】只运行一次订阅【英文标题】:Run subscription only once 【发布时间】:2018-05-22 23:14:55 【问题描述】:

我只需要运行一次userProfiles$.subscribe(async res => 。但它可以无限工作。你能告诉我如何避免它吗?

这是video of that issue

.ts

async loginWithGoogle(): Promise<void> 
    try 
      const result = await this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
      const userId: string = result.additionalUserInfo.profile.id;
      const userProfile: AngularFirestoreDocument<UserProfile> = this.fireStore.doc(`userProfile/$userId`);
      const userProfiles: AngularFirestoreCollection<UserProfile> = this.fireStore.collection('userProfile/', ref => ref.where('email', '==', result.additionalUserInfo.profile.email));
      const userProfiles$: Observable<UserProfile[]> = userProfiles.valueChanges();

      userProfiles$.subscribe(async res =>  //problem is here
        if (res.length == 0) 
          await userProfile.set(
            id: userId,
            email: result.additionalUserInfo.profile.email,
            creationTime: moment().format(),
            lastSignInTime: moment().format()
          );
         else 
          await userProfile.update(
            lastSignInTime: moment().format()
          );
        
      );
    
    catch (err) 
      console.log(err);
    
  

我已尝试将其转换为promise,如下所示。但没有区别。也许我做错了?

 userProfiles$.map(async res => 
        if (res.length == 0) 
          await userProfile.set(
            id: userId, email: result.additionalUserInfo.profile.email,
            creationTime: moment().format(),
            lastSignInTime: moment().format()
          );
        
      ).toPromise();

版本:

 "typescript": "2.4.2"
 "rxjs": "5.5.2",

【问题讨论】:

您是否考虑过将Observable 转换为只执行一次的Promise 你也可以.take(1)收到一个值后强制订阅完成。 我试过了。但没有区别。请查看更新@Szarik @Sampath 也许在observer 上调用.complete() 可以工作吗? usubscribe()取数据后的对象 【参考方案1】:

承诺方式:

userProfiles$.toPromise().then((res) => 
   if (res.length == 0) 
      await userProfile.set(
         id: userId, email: result.additionalUserInfo.profile.email,
         creationTime: moment().format(),
         lastSignInTime: moment().format()
      );
   
).catch(err => 
   // handle error
);

首先你把它转换成promise,然后通过.then()方法监听它并等待resolved的promise。

可观察的.take(1)

userProfiles$.take(1).subscribe(async res =>  //problem is here
        if (res.length == 0) 
          await userProfile.set(
            id: userId,
            email: result.additionalUserInfo.profile.email,
            creationTime: moment().format(),
            lastSignInTime: moment().format()
          );
         else 
          await userProfile.update(
            lastSignInTime: moment().format()
          );
        
      );

注意toPromise 方法不要忘记从 rxjs 运算符导入 toPromise,对于 take,您还应该从 rxjs 导入 take 方法

更新。角度版本 >= 6

由于 Angular 6 需要 rxjs >= 6。现在 take 等运算符现在可以导入并用于 .pipe() 方法。 You can read more here

// somewhere at the top of file
import  take  from 'rxjs/operators';

userProfiles$.pipe(take(1)).subscribe(async res =>  //problem is here
        if (res.length == 0) 
          await userProfile.set(
            id: userId,
            email: result.additionalUserInfo.profile.email,
            creationTime: moment().format(),
            lastSignInTime: moment().format()
          );
         else 
          await userProfile.update(
            lastSignInTime: moment().format()
          );
        
      );

【讨论】:

这不起作用[ts] Property 'take' does not exist on type 'Observable&lt;UserProfile[]&gt;'.。我已经像这样导入了它import map, first, take from 'rxjs/operators'; 你的typescriptrxjs是什么版本? 只是好奇尝试使用这个(userProfiles$ as any).take(1)... 并检查它是否正常工作。这不是最好的解决方案,但我只是好奇。 Promise 方式根本行不通。也没有错误。只是不要触发该方法。 这里也是(userProfiles$ as any).take(1)。不要触发那个方法。【参考方案2】:

我在这里找到了一个同时使用Angularfire2firebase native API 的解决方案。

我从Michael here得到了这个解决方案。

Firestore native API

get
get() returns firebase.firestore.QuerySnapshot

Executes the query and returns the results as a QuerySnapshot.

Returns
non-null firebase.firestore.QuerySnapshot A promise that will be resolved with the results of the query.

.ts

 constructor() 
     this.db = firebase.firestore();
  

   async loginWithGoogle(): Promise<string> 
        try 
          const response = await this.afAuth.auth.signInWithRedirect(new firebase.auth.GoogleAuthProvider());
          const result = await this.afAuth.auth.getRedirectResult();
          this.user = result.user;
          const userId: string = result.user.uid;
          const userProfile: AngularFirestoreDocument<UserProfile> = this.fireStore.doc(`userProfile/$userId`);
          const res = await this.db.collection("userProfiles").where("email", "==", data).get();
          if (res.docs.length === 0) 
            await userProfile.set(
              id: userId,
              email: result.additionalUserInfo.profile.email,
              creationTime: moment().format(),
              lastSignInTime: moment().format()
            );
           else 
            await userProfile.update(
              lastSignInTime: moment().format()
            );
          
          return result.additionalUserInfo.profile.email;
        
        catch (err) 
          console.log(err);
        
      

【讨论】:

以上是关于只运行一次订阅的主要内容,如果未能解决你的问题,请参考以下文章

paho-mqtt实现多客户端订阅一个主题,并保证消息只被接收一次

只订阅多个 FormControl(s) 一次

RxJS - 只订阅一次但不完成 Observable

在 Angular 7 中,为啥即使只订阅一次 HttpClient.post 也会执行两次?

NgrxStore 和 Angular - 大量使用异步管道或在构造函数中只订阅一次

RxJava 作为事件总线被多次调用,即使只触发一次