Javascript-форум (https://javascript.ru/forum/)
-   Angular.js (https://javascript.ru/forum/angular/)
-   -   Дождаться завершения HTTP-запроса, но не всегда (https://javascript.ru/forum/angular/77052-dozhdatsya-zaversheniya-http-zaprosa-no-ne-vsegda.html)

kotelok 19.03.2019 13:12

Дождаться завершения HTTP-запроса, но не всегда
 
Подскажите, как правильно реализовать следующую логику (см код):
1. Если есть закэшированный конфиг, то пропустить его запрос с сервера и сразу перейти к выполнению последующего кода (последняя строка).
2. Если НЕТ закэшированного конфига, то запросить его с сервера, дождаться (!) результата и, если ок, то сохранить в кэш и после этого перейти к последующему коду (последняя строка). Если ошибка - завершить выполнение.

В том виде, что сейчас написано работает некорректно, т.е. 'console.info' начинает выполняться ещё до получения ответа по 'httpClient.get'.

При этом помещать 'console.info' внутерь подписки тоже нельзя, т.к. в этом случае он не выполнится, если данные изначально есть в кэше.

Дублировать вызов тоже как-то неправильно с точки зрения организации кода.

Собственно, какой синтаксис позволит запустить 'httpClient.get' синхронно, т.е. чтобы до его полного завершения не происходило выполнения последующего кода этого метода?

----
        if (this.appConfig.authServerConfig == null) {
            this.httpClient.get("{auth}/.well-known/openid-configuration").subscribe((response:string) => {
                this.appConfig.authServerConfig = response;
            },
            (httpError:HttpErrorResponse) => {
                console.error("error: get auth server config");
            });
        }

        console.info(this.appConfig.authServerConfig["token_endpoint"]);

Nexus 19.03.2019 13:22

Тут, по-моему, неплохим решением было бы возвращать Promise.

methodName(){
    if( hasCachedResponse )
        return Promise.resolve(cachedResponse);

    return new Promise(resolve=>{
        makeRequest().subscribe(response=>{
            cacheResponse(response);

            resolve(response);
        });
    });
}

kotelok 19.03.2019 14:07

Получилась такая вот странная конструкция для подгрузки конфига, если его нет в кэше:
----
    private getAuthConfig(): Observable<boolean> {
        if (this.appConfig.authServerConfig != null) {
            return Observable.of(true);
        }

        return this.httpClient.get("{auth}/.well-known/openid-configuration").flatMap((response: string) => {
            this.appConfig.authServerConfig = response;
            return Observable.of(true);
        }).catch(error => {
            return Observable.of(false);
        });
    }


И такой код, для его вызова:
----
        this.getAuthConfig().subscribe((config) => {

            if (config != true) {
                console.error("error: auth server config");
                return;
            }

            console.info(this.appConfig.authServerConfig["token_endpoint"]);
        });


Но как-то очень уж громоздко выглядит для простой логики.

И ещё возникает проблема, когда требуется несколько http-запросов последовательно выполнить в рамках одного блока логики, т.е.:
1. Что-то забрать с сервера по http-get.
2. После п.1. отправить первый http-post.
3. После п.2. что-то сделать с полученным данными, выполнить следующий http-post.
4. После п.3 произвести обработку данных для отображения пользователю.

У меня, почему-то, каждый раз получаются многоуровневые вложенные конструкции, которые сложно читать. Т.е. по логике кода - одноуровневые последовательные запросы, а реализация - многовложенная. Можно это как-то обойти или это специфика JS?

destus 19.03.2019 14:08

kotelok,
Ну раз это конфиг, и запрашивается с сервера он видимо 1 раз, то можно использовать APP_INITIALIZER хук https://blog.zverit.com/frontend/201...ervice-method/
Кстати вместо этого
return new Promise(resolve=>{
        makeRequest().subscribe(response=>{
            cacheResponse(response);

            resolve(response);
        });
    });

можно использовать toPromise() метод, который определен на Observable классе.

kotelok 19.03.2019 14:12

Цитата:

Сообщение от destus (Сообщение 504998)
kotelok,
Ну раз это конфиг, и запрашивается с сервера он видимо 1 раз, то можно использовать APP_INITIALIZER

Это понятно. Просто взял наиболее простой пример. Но в общем случае меня интересовало то, что в следующем посте обозначил - как не плодя вложенность реализовывать в рамках одного метода последовательное выполнение http-запросов. Т.е. когда следующий вызов зависит от результата выполнение предыдущего запроса.

destus 19.03.2019 14:17

Цитата:

Но в общем случае меня интересовало то, что в следующем посте обозначил - как не плодя вложенность реализовывать в рамках одного метода последовательное выполнение http-запросов. Т.е. когда следующий вызов зависит от результата выполнение предыдущего запроса.
mergeMap, switchMap в помощь

kotelok 19.03.2019 15:32

В общем, попробовал по аналогии с C#. Похоже, в TS тоже есть какая-то реализация 'await' и, судя по поведению, работает она похожим образом. Т.е. пока await-блок не завершится, следующий за ним оператор ожидает своей очереди. Последовательные (зависимые) http-вызовы при таком подходе записываются намного нагляднее. При этом сам метод возвращает управление после первого же 'await', а весь остальной хвостик продолжает выполнение параллельно.
----
        //
        await (async () => {
            if (this.appConfig.authServerConfig != null) {
                return null;
            }
            
            return this.httpClient.get("{auth}/.well-known/openid-configuration").toPromise().then((response:string) => {
                this.appConfig.authServerConfig = response;
            }, (error:HttpErrorResponse) => {
                console.info(error);
            });
        })();

        //
        if (this.appConfig.authServerConfig == null) {
            return;
        }

        //
        console.info(this.appConfig.authServerConfig);


Часовой пояс GMT +3, время: 04:21.