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 :)

Answers:

Answer

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.

Answer

I did manage to find a working solution. I'm not very familiar with the whole Promise and async concept in JavaScript, so this may not be the best solution, but it does the job for my specific case. Here is the code for anyone else looking to implement something similar:

The sample ServiceA remains the same as above:

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
   }
}

Here's the modified ApiService that returns promises for the output and throttles the actual function execution:

export class ApiService {
   // We keep the functions that need to be executed in this queue
   // and process them sequentially
   queue: (() => void)[] = [];

   public async RetrieveA(param1, param2): Promise<number> {
      // This resolver serves two purposes - it will be called when the
      // function is executed (to set the output), but will also be part
      // of the Promise that will be returned to the caller (so that the
      // caller can await for the result).
      let resolver: (value: number) => void;

      // This function will be executed by the RunQueue method when its
      // turn has come. It makes a call to the external API and when that
      // call succeeds - the resolver is called to return the result through
      // the Promise.
      const func = async () => {
         return this.CallApi(param1, param2).then(resolver);
      };

      this.queue.push(func);

      // This is the promise that we return to the caller, so that he
      // can await for the result. 
      const promise = new Promise<number>((resolve, reject) => {
         resolver = resolve;
      });

      return promise;
   }

   public async Run() {
      this.RunQueue(this.queue);
   }

   private async RunQueue(funcQueue: (() => void)[]) {
      // Get the first element of the queue
      const func = funcQueue.shift();

      // If the queue is empty - this method will continue to run
      // until a new element is added to the queue
      if (func) {
         await func();
      }

      // Recursively call the function again after 1 second
      // This will process the next element in the queue
      setTimeout(() => {
         this.RunQueue(funcQueue);
      }, 1000);
   }

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

I hope the comments in the code make it clear what (and how) I'm trying to achieve.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.