Использование узкого места для ограничения скорости запросов API в библиотеке

#javascript #typescript #api #asynchronous #rate-limiting

#javascript #typescript #API #асинхронный #ограничение скорости

Вопрос:

Я пишу оболочку API на TypeScript. Я бы хотел, чтобы код был асинхронным, чтобы максимально соответствовать ограничению скорости рассматриваемого API. API хочет, чтобы запросы отправлялись с максимальной скоростью 1 в секунду.

Я намерен реализовать оболочку API, которая создается один раз и позволяет использовать объекты для достижения разных конечных точек. Например, в более широком API есть post pool конечная точка и . Я хотел бы получить к ним доступ, например post_object.post.submit_request(argument1, ...) , или post_object.pool.submit_request(argument1, ...) .

Я создал вызываемый объект state_info , который передается между различными объектами, внутри которого содержится заголовок user-agent, информация для входа, если она предоставлена, и объект rate-limiter из библиотеки Bottleneck.

Проблема, с которой я сталкиваюсь во время тестирования, заключается в том, что моя программа, похоже, на самом деле не ограничивает скорость запросов; независимо от того, на что я изменяю ограничение в аргументах для Bottleneck, все запросы выполняются примерно за 600 секунд каждый раз.

Я думаю, что это как-то связано с передачей объекта rate-limiter или доступом к нему из нескольких мест, но я не уверен.

Во-первых, вот код для Model объекта, который представляет доступ к API.

 import axios, { AxiosRequestConfig } from "axios";
import { StateInfo, Method } from "./interfaces";

export class Model {
  public stateInfo: StateInfo;

  constructor(stateInfo: StateInfo) {
    // Preserve rate limiter, user agent, etc.
    this.stateInfo = stateInfo;
  }

  //Updated to funcName = () => {} syntax to bind "this" to this class context.
  private submit_request = (query_url: string, method: Method) => {
    if (this.stateInfo.username amp;amp; this.stateInfo.api_key) {
      const axiosConfig: AxiosRequestConfig = {
        method: method,
        url: query_url,
        headers: { "User-Agent": this.stateInfo.userAgent },
        auth: {
          username: this.stateInfo.username,
          password: this.stateInfo.api_key,
        },
      };
      return axios(axiosConfig);
    }  else {
      const axiosConfig: AxiosRequestConfig = {
        method: "get",
        url: query_url,
        headers: { "User-Agent": this.stateInfo.userAgent },
      };

      return axios(axiosConfig);
    }
  };

  public submit_throttled_request = (url: string, method: Method) => {
    return this.stateInfo.rateLimiter.schedule(
      this.submit_request,
      url,
      method
    );
  };
}

  

Затем код, из которого я вызываю этот класс:

 import { Model } from "./models/model";
import Bottleneck from "bottleneck";

const limiter: Bottleneck = new Bottleneck({ mintime: 1000, maxconcurrent: 1 });

const stateInfo = {
  rateLimiter: limiter,
  userAgent: "email@website.com | API Dev",
};

let modelObj: Model = new Model(stateInfo);

async function makeRequest() {
  try {
    let response = await modelObj.submit_throttled_request(
      "https://www.website.com/api",
      "get"
    );
    console.log(response.data.id   "|"   Date.now());
  } catch (err) {
    console.log(err);
  }
}

let start = new Date();
for (let i = 0; i < 20; i  ) {
  makeRequest();
}
  

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

Ответ №1:

Я узнал ответ на свой собственный вопрос после долгих размышлений.

Оказывается, в разделе «подводные камни» ссылки на API bottleneck они отмечают:

Если вы передаете метод объекта как задание, вам, вероятно, потребуется привязать () объект:

с помощью следующего кода:

 // instead of this:
limiter.schedule(object.doSomething);
// do this:
limiter.schedule(object.doSomething.bind(object));
// or, wrap it in an arrow function instead:
limiter.schedule(() => object.doSomething());
  

Это проблема, с которой я столкнулся. Я передавал axios(axiosContext) без привязки области видимости, поэтому ничего не отправлялось в bottleneck ratelimiter. Переносом это так: this.state_info.rateLimiter.schedule(() => axios(axiosContext)); мне удалось правильно привязать контекст по мере необходимости.