Как управлять запросами к API на клиенте?

Какие есть средства для управления количеством запросов? Например, есть API у которого стоит ограничение на один вид запросов не более 100 за 10 сек, на другой 30 за 5 сек.
  • Вопрос задан
  • 176 просмотров
Пригласить эксперта
Ответы на вопрос 1
sergiks
@sergiks Куратор тега JavaScript
♬♬
Писать свой или искать готовый механизм очереди с контролем времени.

В очередь (массив) класть запрос (метод, параметры) и callback. Либо промисы.

Держать массив с временем ответов последних 100 (30) вызовов. Если самый древний более 10 (5) секунд назад, можно выполнить очередной.

Как-то написал вот такую
кривую реализацию


Модуль очереди:
/**
 * Hold Your Horses!
 * Promise-based dispatcher that respects frequency limits.
 * It queues requests so that no more than N are processed within 1 second.
 * Those can go in parallel.
 *
 * Instantiate the new HH with options, specifying time limit.
 * Method .add(function) adds a new job to the queue.
 * Argument function should return Promise object that starts to process only after the function is called.
 * @return Object Promise.
 */

function HorsesHolder(options) {
  options         = options || {};
  this.rps        = options.rps || 3; // requests per second
  this.parallel   = options.parallel || this.rps; // max parallel running jobs

  this.times      = []; // -1: slot is busy, 0: slot is free, positive timestamp - time slot's job has finished
  for (let i=0; i<this.rps; i++) this.times.push(0); // [0, 0, 0] initially

  this.queue      = [];
  this.inprogress = [];
  this.debug      = !!options.debug;
  
  this.debug  &&  console.log("%s ms: [HH] initialized", this.ts());
}


HorsesHolder.prototype.add = function(promiseMaker) {
  var self = this;
  
  return new Promise(function(resolve, reject) {
    self.queue.push({
      resolve: resolve,
      reject: reject,
      promiseMaker: promiseMaker,
    });
    
    self._ping();
  });
};


// Decide: work or wait
HorsesHolder.prototype._ping = function() {

  if (this.queue.length === 0) {
    this.debug  &&  console.log("%s ms: [ping] queue is empty", this.ts());
    return;
  }
  
  const best = this._bestTime();
  
  if (best === -1) {
    this.debug  &&  console.log("%s ms: [ping] cannot go: %s", this.ts(), JSON.stringify(this.times));
    return;
  }
  
  const index = this.times.indexOf(best);
  
  this.debug  &&  console.info("%s ms: [ping] exec now at index %d", this.ts(), index);

  this._execute(index);
}


/**
 * Out of current times[] finds the best to occupy, if possible;
 * otherwise -1
 */
HorsesHolder.prototype._bestTime = function() {
  let best = -1;

  for (let i=0; i<this.rps; i++) {
    const time = this.times[i];
    if (time === 0) return 0;        // can go now - nothing better!
    if (time < 0) continue;          // previous not finished yet
    if (this.ts() < time + 1000) continue; // not yet

    if (best === -1) best = time;
    else best = Math.min(best, time);
  }
  
  return best;
}


HorsesHolder.prototype.ts = function() {
  return (new Date()).getTime();
}


HorsesHolder.prototype._execute = function(index) {
  this.times[index] = -1; // mark busy
  const job = this.queue.shift();
  this.inprogress.push(job);

  const self = this;
  
  job.promiseMaker()
  .then(function(r) {
    self.debug  &&  console.info("%s ms: [HH] Job done at index %d", self.ts(), index);
    job.resolve(r);
  })
  .catch(function(err){
    self.debug  &&  console.error("%s ms: [HH err] Error at index %d: %s", self.ts(), index, err.toString());
    job.reject(err);
  })
  .finally(function(){
    self.inprogress.splice( self.inprogress.indexOf(job), 1);
    self.times[index] = self.ts();
    setTimeout(() => self._ping(), 1000);
  });
}

export default HorsesHolder;


Модуль работы с VK API:
/*global VK*/
/**
 * Function returns Promise for each VK API call.
 * Respects the 3 call per second limit.
 *
 * by Sergei Sokolov <hello@sergeisokolov.com> 2019.
 */
 
import HorsesHolder from '@/utils/horsesholder';

const debug = true;

const HH = new HorsesHolder({ debug });


export default function asyncVK(methodName, data) {

	return HH.add(() => {

		data = data || {};
		if (!data.v) data.v = 5.92; // VK API version https://vk.com/dev/versions
		
		return new Promise((res, rej) => {
			VK.Api.call(
				methodName,
				data,
				r => {
					if (r.error) {
						debug && console.error("[asyncvk] VK API call error:", r.error);
					}
					
					
					if (r.response) {
						
						res(r.response);
						
					} else if (r.error) {
						
						rej(r.error);
						
					} else {
						
						debug && console.error("[asyncvk] VK API bad response:", r);
						
						rej(r);
						
					}
				}
			)
		});
	});
}


Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы