首页 > 解决方案 > Angular 应用程序,使用节流时间限制 api 调用。使用状态来跟踪不同参数的调用

问题描述

我希望能够在 Angular 10 的服务级别上限制我的 api 调用。我找到的关于使用 rxjs throttleTime 的所有答案和所有示例都描述了我们有触发限制的用户交互的场景。像下面的SO帖子

但在我的情况下,用户和组件可以触发相同的服务代码从应用程序的不同位置加载数据。因此,当用户导航到某个页面时,他们将触发呼叫,然后当他们使用下拉菜单时,他们会再次触发它等等。
这是一个昂贵的呼叫,但我仍然需要保持它相对新鲜。我想通过在每次用户与其交互时自动刷新但每次刷新之间有 10 秒的暂停来实现这一点。

可以使用不同的值调用 api 端点。所以endpoint/1将返回一个不同的值,这endpoint/2 意味着一个平坦的无意识油门会使人们在 10 秒内得到错误的数据,然后才允许再次调用 api 并刷新视图状态。这反过来又会增加我必须在我的应用程序的任何地方创建的节流代码的复杂性。

所以我认为我可以将节流集中到服务中,而不是像 SO 帖子示例那样实现节流代码。

但是在这一点上,我的 rxjs/angular 技能已经碰壁了,我的想法变得不可能了。

我试图做的是在服务上创建一个字典,该字典根据提供给 api 的参数进行索引,然后能够返回组件可以订阅的 observable。这将导致服务具有每个参数选项的“状态”字典。

该代码已脱离某些上下文,并且已重命名。

class demoState {
  triggerSubject$: Subject<void>;
  callApi$: Observable<any>;

  constructor(callApiObservable: Observable<any>) {
    this.triggerSubject$ = new Subject<void>();

    this.callApi$ = this.triggerSubject$.pipe(
      throttleTime(1000),
      switchMap(_ => callApiObservable)
    );
  }

  getRessourceUsingTrigger(): Observable<any> {
    // this is the first time I get "stumbed" I don't know a good way to delay the trigger 
    // of the subject until the end component subscribes.
    return of(0).pipe(
      // this will trigger before the switchmap is hit and therefore it goes into the void
      map(_ => this.triggerSubject$.next(void 0)),
      // so this doesn't matter because its underlying observable it never triggered.
      switchMap(_ => this.callApi$)
    );
  }
}

@Injectable({
  providedIn: 'root'
})
export class ThrottleService {
  private testEndpointTrigger: { [testId: number]: demoState } = {};
  constructor() { }

  getFromTest(testId: number): Observable<any> {
    if(this.testEndpointTrigger[testId] === undefined){
      this.testEndpointTrigger[testId] = new demoState(this.GetFromAPI(testId));
    }
    return this.testEndpointTrigger[testId].getRessourceUsingTrigger();
  }

  private GetFromAPI(test: number): Observable<any> {
    return of(test);
  }

}

@Component({
  ...
})
export class PasswordChangeComponent  {

  constructor(private throttleService: ThrottleService){}

   usingServiceOnUserButtonClick(testId: number){
    this.throttleService.getFromTest(testId).subscribe(o => {
      console.log('do stuff with the values ', o);
    });
  }
}

我希望你能帮助我意识到我做错了什么,或者帮助我朝着不同的方向实现这一目标。


在 bryan60 发表第一条评论后进行编辑。我会试着退后一步,从那里开始改写一下。

我有一个来自我的 Angular 应用程序的 api 调用,这很昂贵。我位于“Service1”。我希望在用户导航(组件在 oninit 中调用它)或用户与 ui 交互时不调用它。数据在应用程序的不同页面上使用,可用于填充下拉菜单,但它的变化足以让我希望它相对新鲜(交互时每 10 秒更新一次)。

ApiCall 将根据用户输入生成不同的结果。因此,当我们导航到一个页面时,默认参数是myapi/endpoint/1,然后用户可以更改它,我们会调用myapi/endpoint/2. 此时它需要忽略 10 秒的“限制”,因为我们需要获取应用程序中以前不存在的数据。如果用户要切换回.../1在 10 秒之前触发的值,我们将不需要调用 api,并显示一些缓存数据。

我展示的代码是错误的,它没有尝试捕获等式的“缓存”方面,因为我希望节流首先起作用。

服务需要根据给定的输入“速率限制”。我希望它在服务中而不是调用者中,以避免在每个组件中都有节流逻辑。我的想法是该组件需要访问某种历史记录,以了解以前发送的 api 参数。

我环顾 rxjs 并认为throttletime 听起来像是一个很好的解决方案,但我一直无法找到一个没有为每个触发事件提供直接用户交互的示例。就像自动完成或按钮点击一样。

标签: angularapirxjsthrottling

解决方案


一个可能的解决方案

优先节流时间

这是一个自定义运算符,可让您根据键上的值进行节流。这意味着无论您拥有哪种类型的流,您都需要在使用此运算符之前将其映射(转换)为正确的形状。

这是所需的形状(请注意,键“优先级”可以从其默认值更改)。

{
  ...payload,
  priority: string
}

如果此操作员看到任何没有所需形状的值,它们会被限制在一个default类别下,并且这些值的priorityThrottleTime行为就像常规一样。throttleTime

您可以设置默认字符串以确保它不会与您的优先级字符串发生冲突。

function priorityThrottleTime<T>(
  thrTime: number,
  priorityStr = "priority",
  defaultStr = "default"
): MonoTypeOperatorFunction<T> {
  return s => defer(() => {
    const priorityTimeStamp = new Map<string, number>();
    return s.pipe(
      filter(v => Date.now() - (
        priorityTimeStamp.get(v[priorityStr]) || 
        priorityTimeStamp.get(defaultStr) ||
        0) >= thrTime
      ),
      tap(v => priorityTimeStamp.set(
        v[priorityStr] || defaultStr, 
        Date.now())
      )
    );
  });
}

我没有花时间彻底测试这个,所以你会想尝试一下并解决问题。

一个例子

这里我取了一些字符串,并根据每个字符串的第一个字母进行节流。

from(["Hey", "Jude,", "don't",
  "make", "it", "bad", "take",
  "a", "sad", "song", "and",
  "make", "it", "Better"
]).pipe(
  // Map string into an object that 
  // priorityThrottleTime can use
  map(lyric => ({
    lyric,
    priority: lyric.substring(0, 1)
  })),
  priorityThrottleTime(1000),
  // Map back into a string
  map(({ lyric }) => lyric)
).subscribe(console.log);

如果您的排放已经是对象,您可以提供priorityThrottleTime与这些对象上的键匹配的第二个参数,或者使用上述技术生成自定义优先级字符串。


推荐阅读