Обеспечение успешного выполнения одного метода в зависимости от другого - PullRequest
0 голосов
/ 06 февраля 2020

У меня есть ситуация, когда мне нужно, чтобы в сервисе было установлено несколько полей, прежде чем я смогу выполнить HTTP-вызов. Эти поля получены из другого HTTP-вызова, поэтому я не могу запустить их асинхронно - без этих данных второй вызов завершится неудачно. Я думаю, у меня есть способ сделать это, но я что-то упускаю - первый метод (getSystemConfiguration()) просто не запускается, хотя к концу цепочки определенно есть вызов subscribe().

Я немного новичок в Rx Js, поэтому я чувствую, что это должно быть просто, но чего мне не хватает?

// Method that gets system configuration
setApiInformation(): Observable<void> {
    if (!this.apiKey || !this.checkUrl) {
        return this.systemConfigurationService.getSystemConfiguration()
            .pipe(
                map(config => {

                // None of this ever executes
                this.apiKey = config.apiKey;
                this.checkUrl = config.objectExistsUrl;
            }));
    } else {
        return of();
    }
}

// Method I'm trying to run
checkServerData(data: RequestParameters): Observable<ServerResponseData> {
    return this.setApiInformation()
        .pipe(map(() => {
            let httpOptions = { headers: new HttpHeaders({ "serverApiKey" : this.apiKey }) };
            return this.httpClient.put<ServerResponseData>(this.checkUrl, request, this.httpOptions);
        }));
}

// Ultimate caller
let params: RequestParameters = { tenantId: 1, requestorId: 1 };
this.service.checkServerData(params)
   .subscribe((response: ServerResponseData) => { this.dataExists = response.success; });

Ответы [ 2 ]

1 голос
/ 06 февраля 2020

Итак, здесь есть пара вещей. Во-первых, у вашей наблюдаемой цепочки есть проблемы, вы, как правило, хотите оставить вещи ВНУТРИ наблюдаемой последовательности, и вы хотите сделать это повторяемым образом. Я мог бы переписать это так:

interface Config {
  apiKey: string
  checkUrl: string
}

@Injectable()    
export class MyService {
  private configSource = new ReplaySubject<Config>(1) // stores config

  private setApiInformation() { // private method to load config, call once
    this.systemConfigurationService.getSystemConfiguration().subscribe(this.configSource)
  }

  // all calls go through here to ensure config is ready
  private awaitConfig<T>(action$: (config: Config) => Observable<T>) {
    return this.configSource.pipe(
      first(),
      switchMap(client => action$(client)) // switchMap subscribes to inner observables
    )
  }

  constructor(private systemConfigurationService: SystemConfigurationService) {
    this.setApiInformation() // call once here
  }

  checkServerData(data: RequestParameters): Observable<ServerResponseData> {
    return this.awaitConfig(config => { // use config from here
      let httpOptions = { headers: new HttpHeaders({ "serverApiKey" : config.apiKey }) };
      return this.httpClient.put<ServerResponseData>(config.checkUrl, request, this.httpOptions);
    }));
  }
}

Это хороший способ решить эту проблему для всех вызовов, требующих чего-то асинхронного, теоретически вы могли бы написать базовый сервис, из которого остальные расширяют или, предпочтительно, внедрять и использовать чтобы сделать их вызовы, НО, если это вещь всего приложения, вам лучше использовать APP_INITIALIZER, чтобы убедиться, что что-то сделано (например, загрузка конфигурации) до запуска остальной части приложения.

1 голос
/ 06 февраля 2020

Есть несколько rxjs операторов, которые могут помочь вам в этом. я лично предпочитаю switchMap. и вот как я бы это реализовал:

this.setApiInformation().pipe(switchMap(data => {
  // do something here if needed
  return this.checkServerData();
})).subscribe( data => {
  // do something
});
...