Angular 7: функция ожидания в перехватчике

Я создал перехватчик ошибок в своем первом приложении Angular, которое для меня является новым. Перехватчик пытается обновить токен авторизации Firebase, когда появляется код ответа 401. Поэтому я написал следующий код:

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authService: AuthService, private alertService: AlertService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    return throwError(err.error);
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    return throwError(err.error);
                }
                const reference = this;
                this.authService.getToken(currentUser, true).then(t => {
                    // How do I await and return this properly?
                    return reference.updateTokenAndRetry(request, next, currentUser, t);
                }); // Get token and refresh
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            return throwError(err.error);
        })
    );
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}

Токен обновляется нормально. Однако сетевой вызов не выполняется после обновления, что должно выполняться reference.updateTokenAndRetry(request, next, currentUser, t);.

Я предполагаю, что причина этого в том, что this.authService.getToken(currentUser, true) возвращает Promise (это плагин Firebase, и его нельзя изменить). Я хочу вернуть return reference.updateTokenAndRetry(request, next, currentUser, t);, но это невозможно, так как он находится в асинхронном функциональном блоке.

Как я могу дождаться или ответить на следующий сетевой вызов? Я не могу сделать функцию interceptasync. Я довольно застрял на этом этапе.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
5
0
2 138
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вместо того, чтобы пытаться вернуть асинхронное обещание, вы должны преобразовать свое обещание в наблюдаемое с помощью оператора from RxJS, как описано в этом посте: Преобразовать обещание в наблюдаемое.

Это приведет к правильному возвращаемому типу Observable> для вашего перехватчика.

Ваш код будет выглядеть примерно так (при условии, что вы отправляете только один запрос за раз):

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    return throwError(err.error);
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    return throwError(err.error);
                }
                // Return a newly created function here
                return this.refreshToken(currentUser, request, next);
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            return throwError(err.error);
        })
    );
}

refreshToken(currentUser: any, request: any, next: any) {
    // By making use of the from operator of RxJS convert the promise to an observable
    return from(this.authService.getToken(currentUser, true)).pipe(
        switchMap(t => this.updateTokenAndRetry(request, next, currentUser, t))
    )
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}

Надеюсь это поможет!

@GiovanniTerlingen Это будет хорошо работать только с одним запросом за раз. При обработке нескольких вызовов и ошибок аутентификации ваш метод refreshToken также будет вызываться несколько раз.

Mark Verkiel 29.05.2019 14:22

@Пилатус, интересно. Что я могу сделать против этого? Не стесняйтесь добавлять свой ответ.

gi097 29.05.2019 14:23

Вы можете поместить токен в объект поведения (таким образом вы все еще можете вернуть наблюдаемое и получить последнее значение). Кроме того, вы должны автоматически обновить свой токен (через x минут, но до истечения срока его действия. См. auth0.com/docs/quickstart/spa/angular2/05-токен-обновление для примера кода)

Mr.wiseguy 29.05.2019 14:35

@ Mr.wiseguy, вы правы, единственная проблема заключается в том, что пользователь возвращается после истечения срока действия токена. Запросы, сделанные в intercepter, все еще должны дождаться первоначального вызова refreshtoken.

Mark Verkiel 29.05.2019 14:46

Решение Arwin работает хорошо, но только в среде, где за один раз отправляется один запрос.

Чтобы заставить это работать, сохраните метод refreshToken в Observable с трубой share. Это позволит нескольким подписчикам, но одному результату.

Оберните метод next.handle(request) другим Subject<any> и верните тему. Если запрос запускает вызов ошибки, который не является 401 вызовом ошибки subject.error.

После обновления токена вызовите this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);, чтобы убедиться, что запрос возвращен первоначальному подписчику.

Код ниже является псевдокодом и должен работать в вашем случае.

refreshTokenObservable: Observable<any>;

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    let subject = new Subject<any>();

    next.handle(request).pipe(
        catchError(err => {
            if (err.status === 401) {
                let user = localStorage.getItem('currentUser');
                if (!user) {
                    this.logout(false);
                    subject.error(err.error);
                    return;
                }
                let currentUser = JSON.parse(user);
                if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
                    this.logout(false);
                    subject.error(err.error);
                    return;
                }
                // Return a newly created function here
                this.refreshToken(currentUser).subscribe(token => {

                    this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
                    this.refreshTokenObservable = null; // clear observable for next failed login attempt
                });
            }
            this.alertService.showAlert({
                text: 'Fout tijdens het verzenden van het verzoek',
            });
            subject.error(err.error);
        })
    ).subscribe(result => subject.next(result));

    return subject.asObservable();
}

refreshToken(currentUser: any) {

    if (this.refreshTokenObservable == null)
    {
        // By making use of the from operator of RxJS convert the promise to an observable
        this.refreshTokenObservable = from(this.authService.getToken(currentUser, true)).pipe(share());
    }

    return this.refreshTokenObservable;
}

updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
    // Update local stored user
    currentUser.stsTokenManager.accessToken = token;
    localStorage.setItem('currentUser', JSON.stringify(currentUser));

    // Add the new token to the request
    request = request.clone({
        setHeaders: {
            Authorization: token,
        },
    });

    return next.handle(request);
}

Другие вопросы по теме