Angular 可取消的请求链

Angular 可取消的请求链,angular,rxjs,observable,reactive-programming,switchmap,Angular,Rxjs,Observable,Reactive Programming,Switchmap,所以,我试着把我的脑袋绕到rxjs/observable,在我的测试案例研究中,我试着构建一个简单的基于角度(5)的web应用程序,它应该显示一些关于地理实体的统计信息,即国家、州、城市。因此,我创建了changeView方法,该方法接受三个参数(两个可选参数)和各自的ID。 开始时,我希望应用程序首先在我的模型中检查国家/地区数据是否已加载,如果未加载,则查询数据,将检索到的数据保存在模型中,然后检查是否指定了stateId,是否包含在country.states数组中,然后检查是否已加载,如

所以,我试着把我的脑袋绕到
rxjs/observable
,在我的测试案例研究中,我试着构建一个简单的基于角度(5)的web应用程序,它应该显示一些关于地理实体的统计信息,即国家、州、城市。因此,我创建了
changeView
方法,该方法接受三个参数(两个可选参数)和各自的ID。
开始时,我希望应用程序首先在我的模型中检查国家/地区数据是否已加载,如果未加载,则查询数据,将检索到的数据保存在模型中,然后检查是否指定了
stateId
,是否包含在
country.states
数组中,然后检查是否已加载,如果不查询状态等等,直到检索到所有数据,或者用户更改了输入参数,在这种情况下,所有正在进行的请求都应该停止(据我所知,这部分是由
switchMap
操作员完成的),新一轮请求应该开始。几天后,我明白这太复杂了,所以我改变了计划,决定只在最后将数据存储到模型中。它看起来像我想要的那样,但是子序列请求没有被取消。以下是我目前拥有的内容的摘录:

of({countryId: 1, stateId: 1, cityId: 1}).pipe(
  debounceTime(500),
  switchMap(newState => {
    if ((newState !== null))
    {
     return this.geoStatsService.getCountry(newState.countryId);
    }
    else
    {
     return empty();
    }
  }, (outerValue, innerValue) => {
    if ("stateId" in outerValue && outerValue.stateId !== undefined)
    {
     // ToDo: Check that state is located in this county
     return forkJoin(of(innerValue), this.geoStatsService.getStates(outerValue.stateId));
    }
    else
    {
     return of(innerValue);
    }
  }),
  mergeAll()
  ).subscribe(res => {
     console.debug("---Subscription---");
     console.debug(res);
  });
我已经看到,一旦第二个请求被触发,它将不会被取消,并且数据将进入订阅。。。在我看来,我把它复杂化了,可以用更优雅的方式来做,这也会像我预期的那样,是吗? 补充问题:
我可以,或者更精确的问题-我应该从流中提取数据,还是仅在订阅方法中提取数据?

Hmmm。。。鉴于国家/州/城市可能都需要加载,在我看来,您需要的是3个嵌套的开关地图,如下所示:

interface Query { countryId: number; stateId?: number; cityId?: number }

// keep a cache to avoid reloading
const countryCache = new Map<number, Country>();
const stateCache = new Map<number, State>();
const cityCache = new Map<number, City>();

const query$: Observable<Query> = of({countryId: 1, stateId: 1, cityId: 1});

query$.pipe(
  debounceTime(500),
  switchMap((q: Query) => {
    if (!q || q.countryId === undefined) {
      return of([]);
    }

    // get country from cache or load from backend
    const country$ = countryCache.has(q.countryId) ?
      of(countryCache.get(q.countryId)) :
      this.geoStatsService.getCountry(q.countryId).do(country => {
        countryCache.set(q.countryId, country);
      });

    return country$.pipe(
      switchMap(country => {
        if (!country || !country.states.includes(q.stateId)) {
          return of([country]);
        }

        // get state from cache or load from backend
        const state$ = stateCache.has(q.stateId) ?
          of(stateCache.get(q.stateId)) :
          this.geoStatsService.getState(q.stateId).do(state => {
            stateCache.set(q.stateId, state);
          });

        return state$.pipe(
          switchMap(state => {
            if (!state || !state.cities.includes(q.cityId)) {
              return of([country, state]);
            }

            // get city from cache or load from backend
            const city$ = cityCache.has(q.cityId) ?
              of(cityCache.get(q.cityId)) :
              this.geoStatsService.getCity(q.cityId).do(city => {
                cityCache.set(q.cityId, city);
              });

            return city$.map(city => [country, state, city]);
          })
        );
      })
    );
  })
).subscribe(([country, state, city]) => {
  // ...
});
接口查询{countryId:number;stateId?:number;cityId?:number}
//保留缓存以避免重新加载
const countryCache=新映射();
const stateCache=新映射();
const cityCache=新地图();
const query$:Observable=of({countryId:1,stateId:1,cityId:1});
查询$.pipe(
去BounceTime(500),
switchMap((q:Query)=>{
如果(!q | | q.countryId==未定义){
归还([]);
}
//从缓存获取国家/地区或从后端加载
const country$=countryCache.has(q.countryId)?
of(countryCache.get(q.countryId)):
this.geoStatsService.getCountry(q.countryId).do(country=>{
countryCache.set(q.countryId,country);
});
返回国家/地区$.pipe(
开关地图(国家=>{
如果(!country | |!country.states.includes(q.stateId)){
返回([国家]);
}
//从缓存获取状态或从后端加载
const state$=stateCache.has(q.stateId)?
of(stateCache.get(q.stateId)):
this.geoStatsService.getState(q.stateId).do(state=>{
stateCache.set(q.stateId,state);
});
返回状态$.pipe(
开关映射(状态=>{
如果(!state | |!state.cities.includes(q.cityId)){
归还([国家、州]);
}
//从缓存获取城市或从后端加载
const city$=cityCache.has(q.cityId)?
of(cityCache.get(q.cityId)):
this.geoStatsService.getCity(q.cityId).do(city=>{
cityCache.set(q.cityId,city);
});
返回城市$.map(城市=>[国家、州、城市]);
})
);
})
);
})
).订阅(([国家、州、市])=>{
// ...
});

尝试使用“flatMap”而不是“switchMap”, switchMap有自己的取消系统,这就是为什么有时您无法获得订阅


顺便说一句,您必须订阅,否则代码将无法运行。

Wow,因此它在嵌套方面变得过于复杂,。。非常感谢您为编写这个长示例所做的努力和所有工作!至于我的最后一个问题,我看到您从流中提取了数据,这被认为是一种良好的做法吗?是的,在嵌套方面-我认为改进geoStatsService api可能会有所帮助(可能支持在查找国家/地区时快速获取州,或者以某种方式允许在单个api调用中查找国家/州/城市).至于从何处提取数据,我倾向于选择可观察的数据流,而不是可观察的数据流。Well,您并不总是希望给用户提供如此大的数据片段,让他们急切地加载数据,但肯定是为了让服务器端的查找方法更复杂,并返回相关国家和州的数据ong与被请求城市的ong-是一个选项。但对于我的具体示例和如何链接这些请求的理解,您的响应是非常宝贵的。非常感谢!感谢您的回复,但我认为我没有很好地解释我自己,这就是我想要的-取消以前的请求,因为用户已经更改了输入-我不需要显示在info,还有一个调用
subscribe()
最后,你可能没有注意到……这正是switchMap所做的。是的,我没有理解你的问题,我想你在抱怨取消。是的,我知道switchMap的功能是什么,我不知道如何链接它们,反正我已经得到了我需要的答案,但谢谢你的参与我很高兴,也很愿意帮忙