I have an interceptor that check for http response with status code 401 so that it can request refresh-token by calling refreshToken() before trying the initial request once more.
The refreshToken() observe refreshTokenLockSubject$ so that only one refresh-token request can be made, while the others will have to wait for the refresh-token request to be completed before trying the initial requests once more.
For instance, let say I have requests A, B, and C that returned status code 401. Request A got to make refresh-token request, while B and C will wait for A's refresh-token request to be completed before trying their initial requests once more.
@Injectable()
export class HttpHandlerInterceptor implements HttpInterceptor {
  /** unrelated codes **/
  intercept(req: HttpRequest<any>, next: HttpHandler): any {
    /** unrelated codes **/
    return next.handle(req).pipe(
      catchError((err) => {
        // handle unauthorized
        if (err.status == 401) return this.refreshToken(req, next, err);
        return throwError(() => err);
      })
    ) as Observable<any>;
  }
  /** Handle refresh token **/
  refreshToken(
    req: HttpRequest<any>,
    next: HttpHandler,
    err: any
  ): Observable<HttpEvent<any>> {
    return this.authService.refreshTokenLockSubject$.pipe(
      first(),
      switchMap((lock) => {
        // condition unlocked
        if (!lock) {
          this.authService.lockRefreshTokenSubject();
          return this.authService.refreshToken().pipe(
            switchMap(() => {
              this.authService.unlockRefreshTokenSubject();
              return next.handle(req);
            }),
            catchError((err) => {
              /** unrelated codes **/
              return throwError(() => err);
            })
          );
        // condition locked
        } else {
          return this.authService.refreshTokenLockSubject$.pipe(
            // only unlocked can pass through
            filter((lock) => !lock),
            switchMap(() => {
              return next.handle(req);
            }),
            catchError((err) => {
              /** unrelated codes **/
              return throwError(() => err);
            })
          );
        }
      })
    );
  }
}
The refreshTokenLockSubject$ is a boolean behavior subject:
@Injectable()
export class AuthService {
  /** unrelated codes **/
  private _refreshTokenLockSubject$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
  lockRefreshTokenSubject = () => this._refreshTokenLockSubject$.next(true);
  unlockRefreshTokenSubject = () => this._refreshTokenLockSubject$.next(false);
  get refreshTokenLockSubject$(): Observable<boolean> {
    return this._refreshTokenLockSubject$.asObservable();
  }
  // requests A, B, and C were constructed similar to this
  refreshToken = (): Observable<any> =>
    this.http.post<any>(
      this.authEndpoint(AUTH_ENDPOINT['refreshToken']),
      {},
      { withCredentials: true }
    );
}
Continue with the example above (requests A, B, and C), I noticed that only request A finalize get called, while requests B and C were not.
Request A:
this.companyService
    .A(true)
    .pipe(finalize(() => (this.loading = false)))
    .subscribe({
      next: (res) => { /** unrelated codes **/ },
    });
Request B and C:
forkJoin({
  bRes: this.companyService.B(),
  cRes: this.companyService.C()
})
  .pipe(finalize(() => (this.loading = false)))
  .subscribe({
    next: ({ bRes, cRes}) => {
       /** unrelated codes **/
    },
  });
Whether requests B and C are a forkJoin does not have an impact as far as I observed.
The B and C subscription complete callback were also not called.
Does anyone know why finalize is not called?
Besides that, does this mean the B and C subscriptions were never unsubscribed?
 
    