javascriptnode.jstypescriptthrottlingdelayed-execution

How to throttle typescript functions that return output


I am writing a node.js application using typescript. My application will have several services communicating with each other. Several of the services need to call an external API. This API has a restriction on the number of calls per second that can be executed. Because of this I want to create one service that wraps the external API calls (let's call it ApiService). The other services will call this one and it will collect their requests in a queue and execute them sequentially - N requests per second (for simplicity let's assume 1 per second). When service A calls a method of the ApiService - it expects to receive an output (it is fine to receive a Promise).

Now my question is - how to queue these API calls in the ApiService, so that every 1 second the next call in the queue is executed and also return the output of that API call to the caller of the ApiService?

Here's a sample service:

export class ServiceA {
   apiService: ApiService;

   public constructor(_apiService: ApiService) {
      apiService = _apiService;
   }

   public async DoWork() {
      // Do some stuff
      const output: number = await apiService.RetrieveA(param1, param2);
      // Do something with the output
   }
}

The ApiService:

export class ApiService {
   queue: (() => Promise<any>)[] = [];

   public async RetrieveA(param1, param2): Promise<number> {
      const func = async () => {
         return this.CallApi(param1, param2);
      };

      this.queue.push(func);
      return func();
   }

   public async RunQueue() {
      while(true) {
         const func = this.queue.shift();
         if (!func) { continue; }
         // Call the function after 1 second
         await setTimeout(() => { func(); }, 1000);
      }
   }

   private async CallApi(param1, param2): Promise<number> {
      // Call the external API, process its output and return
   }
}

The main method that orchestrates the whole thing:

var CronJob = require('cron').CronJob;

const apiService = new ApiService();
const service = new ServiceA(apiService);

new CronJob('* * * * * *', function() {
   service.DoWork();
}, null, true);

apiService.RunQueue();

The problem I am facing is that when RetrieveA method returns func() - the function gets executed. I need to return a Promise but the actual function execution needs to happen in RunQueue() method. Is there a way to achieve this? Can I return a promise without executing the function right away and upon awaiting this promise - to receive the output when the function is called in the RunQueue method?

Or is there a different approach to solving my issue of throttling API calls that return output?

I am new to the Node.js/Typescript/JavaScript world, so any help is appreciated :)


Solution

  • All of that could be a lot simpler if you want to restrict calls to RetreiveA to 2 per second:

    //lib is here: https://github.com/amsterdamharu/lib/blob/master/src/index.js
    import * as lib from '../../src/index'
    const twoPerSecond = lib.throttlePeriod(2,1000);
    export class ApiService {
      public RetrieveA(param1, param2): Promise<number> {
        //removed the resolver part, according to the typescript signature
        //  it should return a promise of number but resolver actually takes
        //  that number and returns void (undefined?)
        return twoPerSecond(this.CallApi.bind(this))([param1, param2]);
      }
      //change the signature of this function to take one parameter
      //  but deconsruct the array to param1 and param2
      private async CallApi([param1, param2]): Promise<number> {
        // Call the external API, process its output and return
      }
    }
    

    Your method only works if there is only ever one instance of this class. If you were to create multiple instances and call RetrieveA on those instances you no longer limit requests to callApi.