使用Node.js和Promises实现指数补偿

时间:2018-08-29 15:05:38

标签: javascript node.js

我正在用我的代码向API发出约70个请求。我收到一个错误响应,告诉我我一次又一次提出请求,因此我决定使用指数补偿的方法来解决此问题。

目前,这是我的代码:

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

我的getFileName函数向API发出请求并返回Promise。

当前这不起作用,因为Promises是异步的(有点)。我的for循环运行得非常快,并调用了getFileName函数,这使这些API请求变得非常快。然后,它会收到某些API调用的错误,在这种情况下,它将更新backoffTime。此实现无效。

有什么想法可以正确实现吗?

4 个答案:

答案 0 :(得分:2)

首先用几乎无限的循环来阻塞浏览器是一个非常糟糕的主意,只需使用promises:

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

然后在继续循环并使用延迟之前等待承诺:

 (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);
     });
   }
})();

答案 1 :(得分:1)

您可以使用closures来解决此问题,这是一个示例:

for (let i = 0; i < fileIds.length; i++) {
  let fileId = fileIds[i];
  doCall(fileId);
}

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

function doCall(fileId, backoffTime = 1){
  getFileName(fileId, auth)
    .then((name) => {
      // do some work
    })
    .catch((err) => {
      backoff(backoffTime);
      doCall(fileId, (backoffTime * 2))
    });
}

此代码可能导致无限递归,您可能应该添加一些检查以防止这种情况。

答案 2 :(得分:0)

最简单的方法是使用async / await,然后等待每个请求,或者如果它对您来说太慢,则使用15个请求和Promise创建所有块。

您还可以使用以下命令:https://caolan.github.io/async/parallelLimit.js.html

将promise转换为回调,反之亦然,这将需要一些额外的工作,但它将做得最好。

这是函数: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
})

答案 3 :(得分:0)

您可以使用递归重试给定的时间,也可以吸收回复因子。

没有依赖项:

// 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));