【问题标题】:How can two observables run sequentially in function?两个 observables 如何在函数中按顺序运行?
【发布时间】:2021-02-28 06:04:29
【问题描述】:

我希望在单击按钮时运行此函数 getUpcomingEvents()。我将 cmets 放在函数中以帮助解释它,但要点是它接受用户选择的美国城市和州的字符串,并返回该城市所有即将发生的事件的列表。正如您在下面看到的,有两个可观察的订阅,但是问题是当您单击触发此函数调用的按钮时,直到您单击该按钮两三下才会返回所需的数组返回。我相信这是一个时间问题,可观察到一个接一个地发生。我意识到这个函数可能太长了,我想知道我是否还需要在这里使用 switchMap 或其他东西才能让这一切正常工作。如果这里有任何不清楚的地方,请告诉我,我可以澄清。不包含 HTML,因为它只是一个调用函数的按钮,函数中传递了城市和字符串。

  citySearchURL = '';
  metroAreaSearchURL: any;
  metroID = '';
  metroAreaResults: any = [];
  cityResult: any;
  upcomingMetroEvents: any[];
  upcomingCityEvents: any[];
  stateAbbrvs = {
    'AL': 'Alabama',
    'AK': 'Alaska',
    'AS': 'American Samoa',
    'AZ': 'Arizona',
    'AR': 'Arkansas',
    'CA': 'California',
    'CO': 'Colorado',
    'CT': 'Connecticut',
    'DE': 'Delaware',
    'DC': 'District Of Columbia',
    'FM': 'Federated States Of Micronesia',
    'FL': 'Florida',
    'GA': 'Georgia',
    'GU': 'Guam',
    'HI': 'Hawaii',
    'ID': 'Idaho',
    'IL': 'Illinois',
    'IN': 'Indiana',
    'IA': 'Iowa',
    'KS': 'Kansas',
    'KY': 'Kentucky',
    'LA': 'Louisiana',
    'ME': 'Maine',
    'MH': 'Marshall Islands',
    'MD': 'Maryland',
    'MA': 'Massachusetts',
    'MI': 'Michigan',
    'MN': 'Minnesota',
    'MS': 'Mississippi',
    'MO': 'Missouri',
    'MT': 'Montana',
    'NE': 'Nebraska',
    'NV': 'Nevada',
    'NH': 'New Hampshire',
    'NJ': 'New Jersey',
    'NM': 'New Mexico',
    'NY': 'New York',
    'NC': 'North Carolina',
    'ND': 'North Dakota',
    'MP': 'Northern Mariana Islands',
    'OH': 'Ohio',
    'OK': 'Oklahoma',
    'OR': 'Oregon',
    'PW': 'Palau',
    'PA': 'Pennsylvania',
    'PR': 'Puerto Rico',
    'RI': 'Rhode Island',
    'SC': 'South Carolina',
    'SD': 'South Dakota',
    'TN': 'Tennessee',
    'TX': 'Texas',
    'UT': 'Utah',
    'VT': 'Vermont',
    'VI': 'Virgin Islands',
    'VA': 'Virginia',
    'WA': 'Washington',
    'WV': 'West Virginia',
    'WI': 'Wisconsin',
    'WY': 'Wyoming'
};


  constructor(private http: HttpClient) { }

  // Function takes in a user-selected city and a state separated by a comma.
  // Function returns all upcoming events that match the city name.
  getUpcomingEvents(cityAndState: string) {
    const cityStateSplit = cityAndState.split(', ');
    const city = cityStateSplit[0];
    const state = cityStateSplit[1];

    // Create URL to send to songkick API that returns all areas that match the city name.
    this.citySearchURL = 'https://api.songkick.com/api/3.0/search/locations.json?query=' + city + '&apikey=xxxxxxxx';
    const metroAreaObservable = this.http.get(this.citySearchURL);
    metroAreaObservable.subscribe(
      data => {
        // Songkick returns two objects - a city object and a metro area object - that matches the city used in the citySearchURL,
        // one that has city info and one that has it's respective metro area info. Only the metro area object
        // has an "id", which can in turn be used to search for events in that area.
        // Here I assign these two objects to metroAreaResults.
        this.metroAreaResults = data.resultsPage.results.location;

        // Because there may be some cities returned that don't match the desired state as well, Check if user-selected city
        // matches songkicks returned city and state, and pull metro id.
        // The metro id will be used to create a url for another API request.
        for (let i = 0; i < this.metroAreaResults.length; i++) {
          if ((this.metroAreaResults[i].city.displayName === city) &&
            (this.stateAbbrvs[this.metroAreaResults[i].city.state.displayName] === state)) {
              this.cityResult = this.metroAreaResults[i];
              this.metroID = (this.metroAreaResults[i].metroArea.id.toString());
              this.metroAreaSearchURL = 'https://api.songkick.com/api/3.0/metro_areas/' +
                this.metroID + '/calendar.json?apikey=xxxxxxxx';
          }
        }
      },
      error => {
        throw error;
      },
      () => {   }
    );

    const eventsObservable = this.http.get(this.metroAreaSearchURL);
    eventsObservable.subscribe(
      data => {
        // Array of all upcoming events in metro area
        this.upcomingMetroEvents = Array.of(data)[0].resultsPage.results.event;
      },
      error => {
        throw error;
      },
      () => { }
    );

    // Because the end goal of this entire function is to return all of the upcoming events for the
    // user-selected CITY (not metro area), this for loop checks that the upcoming event's city location
    // matches the user selected city, and adds it to an array. After for loop, array gets returned.
    for (let i = 0; i < this.upcomingMetroEvents.length; i++) {
      if ((this.upcomingMetroEvents[i].location.city.split(', ')[0]) === city) {
        this.upcomingCityEvents.push(this.upcomingMetroEvents[i]);
      }
    }
    return this.upcomingCityEvents;
  }

【问题讨论】:

    标签: angular typescript api rxjs observable


    【解决方案1】:

    这是一个典型的异步问题。在第一个 observable 运行的回调之前创建第二个 observable,而第二个 observable 依赖于第一个。

    我试图重构你的代码,这就是我的结果:

    getUpcomingEvents(cityAndState: string) {
        const [city, state] = cityAndState.split(', ');
    
        this.http.get(`https://api.songkick.com/api/3.0/search/locations.json?query=${city}&apikey=xxxxxxxx`).pipe(
          map(data => data.resultsPage.results.location),
          map(metroAreaResults => metroAreaResults.filter(metroAreaResult => metroAreaResult.city.displayName === city)),
          map(metroAreaResults => metroAreaResults.filter(metroAreaResult => this.stateAbbrvs[metroAreaResult.city.state.displayName] === state)),
          map(metroAreaResults => metroAreaResults.map(metroAreaResult => this.http.get(`https://api.songkick.com/api/3.0/metro_areas/${metroAreaResult.metroArea.id.toString()}/calendar.json?apikey=xxxxxxxx`))),
          switchMap(metroAreaResultsRequests => forkJoin(metroAreaResultsRequests.pipe(map(data => data.resultsPage.results.event)))),
          map(upcomingEvents => upcomingEvents.flat()),
        ).subscribe(
          upcomingEvents => this.upcomingCityEvents = upcomingEvents,
          error => throw error,
          () => {},
        );
      }
    

    【讨论】:

    • 感谢您的回复。我正在尝试慢慢重构,以便我可以理解这些概念,所以我使用了 concatMap 运算符——我需要先完成 observable,然后再触发第二个 http.get。你介意看看,让我知道我做错了什么吗?我做了一个显示服务文件和组件的 stackblitz。没有完整的代码库,所以它不会运行,但有所有相关信息。关于如何正确返回事件列表的任何建议都将是一个巨大的帮助。再次感谢。 stackblitz.com/edit/angular-ivy-n59uzv?file=src/app/…
    猜你喜欢
    • 2012-06-13
    • 1970-01-01
    • 2017-09-06
    • 1970-01-01
    • 1970-01-01
    • 2019-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多