简体   繁体   中英

Implementing exponential backoff with Node.js and Promises

I'm making about 70 requests to an API in my code. I'm getting an error response telling me that I'm making requests too quickly one after the other and I have decided to use the idea of exponential backoff to get through this problem.

Currently, this is what my code looks like:

  let backoffTime = 1;

  for (let i = 0; i < fileIds.length; i++) {
    let fileId = fileIds[i];
    getFileName(fileId, auth)
    .then((name) => {
      // do some work
    })
    .catch((err) => {
      // assumes that the error is "request made too soon"
      backoff(backoffTime);
      backoffTime *= 2;
      i--;
      console.log(err);
    });
  }

function backoff(time) {
  let milliseconds = time * 1000;
  let start = (new Date()).getTime();
  while (((new Date()).getTime() - start) < milliseconds) {
    // do nothing
  }
}

My getFileName function makes the request to the API and returns a Promise.

Currently this does not work because Promises are async (kinda). My for loop runs really fast and calls the getFileName function which makes those API requests really fast. Then, it gets the error for some of the API calls in which case it updates the backoffTime. This implementation doesn't work.

Any idea how I can implement this correctly?

First of all blocking the browser with a nearly infinite loop is a very very bad idea, just use promises:

 const delay = ms => new Promise(res => setTimeout(res, ms));

Then just await the promise before continuing the loop and use the delay:

 (async function() {
   for (let i = 0; i < fileIds.length; i++) {
     let fileId = fileIds[i];
     await getFileName(fileId, auth)
      .then((name) => {
        // do some work
      })
      .catch((err) => {
        // assumes that the error is "request made too soon"
        backoffTime *= 2;
        i--;
        console.log(err);
        return delay(backoffTime);
      });
   }
})();

The easiest way is to use async/await and then either await each request, or if it is too slow for you, then create chunks with ie 15 requests and Promise.all such chunks.

You can also use this: https://caolan.github.io/async/parallelLimit.js.html

It will require some extra work to transition promises into callbacks and vice versa, but it will do the best job.

This is the function: parallelLimit(tasks, limit, callback)

const tasks = [];
// This for-cycle will just prepare the tasks into array of functions
for (let i = 0; i < fileIds.length; i++) {
  tasks.push(callback => doWorkThatReturnsPromise(fileIds[i])
     .then(val => callback(null, val))
     .catch(callback));
}

async.parallelLimit(tasks, 15, (err, values) => {
  // do something after it finishes
})

You can use recursion to re-try a given ammount of times, also teak the replying factor.

No dependencies:

 // pass the promise as the attempt property const exponentialBackoff = ({ attempt, maxAttempts = 3, wait = 200, factor = 2, }) => new Promise((resolve, reject) => { setTimeout(async () => { try { resolve(await attempt); } catch (error) { if (maxAttempts) { exponentialBackoff({ attempt, maxAttempts: maxAttempts - 1, wait: wait * factor }) .then(resolve) .catch(reject); } else { reject(error); } } }, wait); }); exponentialBackoff({ attempt: Promise.reject(new Error('Rejected')) }) .catch(err => window.alert(err.message)); 

You can fix that using closures , here is an example:

 for (let i = 0; i < fileIds.length; i++) { let fileId = fileIds[i]; doCall(fileId); } function doCall(fileId, backoffTime = 1){ getFileName(fileId, auth) .then((name) => { // do some work }) .catch((err) => { setTimeout(() => { doCall(fileId, (backoffTime * 2)); }, backoffTime * 1000); }); }

I've replaced the backoff function which pauses the execution using a while loop with a setTimeout call.

This code can cause an infinite recursion, you should probably add some checks to prevent that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM