Приложение Angular, использующее Throttime для ограничения вызовов API. Использование состояния для отслеживания вызовов с различными параметрами

Я хочу иметь возможность регулировать мои вызовы API на уровне обслуживания в Angular 10. Все ответы и все примеры, которые я нахожу по использованию rxjs, threadTime описывает сценарии, в которых у нас есть взаимодействие с пользователем, которое запускает регулирование. Как и следующий SO post

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

Конечная точка API может вызываться с разными значениями. Таким образом, endpoint/1 вернет значение, отличное от endpoint/2. Это означает, что плоская неосведомленная дроссельная заслонка оставит людей с неправильными данными на 10 секунд, прежде чем им будет разрешено снова вызвать API и обновить состояние просмотра. Что, в свою очередь, усложнило бы код дросселя, который мне пришлось бы создавать повсюду в моем приложении.

Поэтому я подумал, что могу централизовать регулирование в сервисе вместо реализации кода дросселирования, как в примере с сообщениями SO.

Но в этот момент я уперся в стену своими навыками rxjs/angular, и мой разум становится невозможным.

Что я пытался сделать, так это иметь словарь службы, который был проиндексирован на основе параметра, заданного API, который затем смог бы вернуть наблюдаемое, на которое компонент мог подписаться. Это приведет к тому, что у службы будет словарь с опцией состояния для каждого параметра.

Код вырван из некоторого контекста, а вещи переименованы.

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. Я постараюсь сделать шаг назад, немного перефразировать и пойти дальше.

У меня есть вызов API из моего приложения angular, который стоит дорого. Я находится в Service1. Я хочу, чтобы он не вызывался всякий раз, когда пользователь перемещается (компонент вызывает его в oninit) или когда пользователь взаимодействует с пользовательским интерфейсом. Данные используются на разных страницах в приложении и могут использоваться для заполнения выпадающих списков, но они достаточно меняются, чтобы я хотел, чтобы они были относительно свежими (обновляются каждые 10 секунд при взаимодействии).

ApiCall будет генерировать разные результаты в зависимости от ввода пользователя. Поэтому, когда мы переходим на страницу, параметр по умолчанию будет myapi/endpoint/1, и пользователь может изменить его, и мы вызовем myapi/endpoint/2. На этом этапе ему нужно игнорировать 10-секундный дроссель, потому что нам нужно получить данные, которых ранее не было в приложении. Если бы пользователь переключился обратно на значение, которое вызвало .../1, до истечения 10 секунд, нам нужно было бы не вызывать API и показывать некоторые кэшированные данные.

Код, который я показал, является неправильным, не пытался зафиксировать кэшированный аспект уравнения, поскольку я хотел, чтобы дросселирование работало в первую очередь.

Службе необходимо ограничить скорость на основе предоставленных входных данных. Я хочу, чтобы это было в службе, а не в вызывающей стороне, чтобы избежать логики дросселя в каждом компоненте. Моя мысль заключалась в том, что компонент должен иметь доступ к какой-то истории того, какие параметры API были отправлены ранее.

Я посмотрел вокруг rxjs и подумал про себя, что threadtime звучит как хорошее решение для этого, но я не смог найти пример, который не имеет прямого взаимодействия с пользователем для каждого инициированного события. Например, автозаполнение или нажатие кнопок.


person Mikkel    schedule 24.03.2021    source источник
comment
это немного в сорняках, не могли бы вы немного отступить и попытаться просто дать некоторые требования для этой службы дроссельной заслонки, которую вы пытаетесь достичь   -  person bryan60    schedule 24.03.2021
comment
Ах, ладно, я попытался добавить немного текста в конце, чтобы уточнить требования.   -  person Mikkel    schedule 25.03.2021


Ответы (1)


Возможное решение

приоритетдроссельное время

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

Это необходимая форма (обратите внимание, что ключ «приоритет» можно изменить по умолчанию).

{
  ...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 второй аргумент, который соответствует ключу этих объектов, или использовать описанный выше метод для создания пользовательской строки приоритета.

person Mrk Sef    schedule 25.03.2021