Node.js API Request limit with request-promise

I am trying to hit REST API endpoints from my node.js app, as follows:

var people = [];
for(var i=0; i<n; i++) {
    //create person
    people.push(person);
}
return Promise.all(people.map(create3APIRequestPromises));

where create3APIRequestPromises is approximately the following:

// APIRequestPromise = require('request-promise')(options);
return Promise.all([APIRequestPromise1, APIRequestPromise2, APIRequestPromise3]);

The code all works properly for a small number of people, but if I increase it too much, it begins to fail. I believe this failure is caused by the service providing the REST API throttling my usage. So, my question is what is the best way to limit the number of requests I send to say, 10 per second.

I have read about node-rate-limiter, but I couldn't see how it fit in with the promises I have written above using 'request-promise' (maybe it isn't possible with this module, so perhaps you could suggest an alternative).

Thanks.

Answers:

Answer

I think that in your case is better to use simple time padding between making requests. To do it you should to split requests into series and prepend each group with delay. I realise example. Usage is:

// Create timepad function
let timePad = createTimePad(5, 10e3); // 5 requests each 10 seconds

// Iterate over people
return Promise.all(
  people.map(
    (human) => timePad().then(() => create3APIRequestPromises(human))
  )
);

The function for time pad creation:

// Create timepad function where timeout is timeout between calls and series is the maximum
// number of calls that will be done simultaneously
function createTimePad(series = 10, timeout = 1000) {
  let seriesCounter = series;
  let delay = timeout;

  return () => {
    return new Promise((resolve) => {
      if (seriesCounter < 1) {
        delay += timeout;
        seriesCounter = series;
      }

      setTimeout(resolve, delay);

      seriesCounter -= 1;
    });
  };
}

It's pretty simple and split requests into groups with variable size on your wish.

Answer

Well, that is really a good question. I had the same issue, realized that no one solved it yet, and started to implement quota. It already covers your use case.

Create your manager like this:

var quota = require('quota');

var manager = new quota.Manager({
    backoff: 'timeout'
});

// 10 new requests per second
manager.addRule({
    name: 'main',
    limit: 10,
    window: 1000,
    throttling: 'window-sliding',
    queueing: 'fifo',
    resource: 'requests'
});

var quotaServer = new quota.Server();
quotaServer.addManager('custom', manager);

var quotaClient = new quota.Client(quotaServer);

Then call each of your requests through this wrapper:

var request = require('request-promise');

function requestThrottled(options) {

    var _grant;

    return quotaClient.requestQuota('custom', {}, { requests: 1 }, {
        maxWait: 60000 // Each request will be queued for 60 seconds and discarded if it didn't get a slot to be executed until then
    })
            .then(function (grant) {

                _grant = grant;

                return request(options);

            })
            .finally(function () {

                if (_grant) {
                    _grant.dismiss();
                }

            });

}

You can make your requests as before:

Promise.all([
    requestThrottled(optionsRequest1),
    requestThrottled(optionsRequest2),
    requestThrottled(optionsRequest3)
])

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.