#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));
мне удалось правильно привязать контекст по мере необходимости.