如何在不阻塞节点的情况下重复请求直到成功?

问题描述 投票:0回答:10

我有一个接受参数和回调的函数。它应该向远程 API 发出请求并根据参数获取一些信息。当它获取信息时,它需要将其发送到回调。现在,远程 API 有时无法提供。我需要我的函数不断尝试,直到它成功完成,然后然后用正确的数据调用回调。

目前,我在函数中有以下代码,但我认为像 while (

!done
);不是正确的节点代码。

var history = {};
while (true) {
    var done = false;
    var retry = true;
    var req = https.request(options, function(res) {
        var acc = "";
        res.on("data", function(msg) {
            acc += msg.toString("utf-8");
        });
        res.on("end", function() {
            done = true;
            history = JSON.parse(acc);
            if (history.success) {
                retry = false;
            }
        });
    });
    req.end();
    while (!done);
    if (!retry) break;
}
callback(history);

如何以正确的方式做到这一点?

javascript node.js
10个回答
53
投票

无需重新发明轮子......您可以使用流行的异步实用程序库,在这种情况下为“重试”方法。

// try calling apiMethod 3 times
async.retry(3, apiMethod, function(err, result) {
    // do something with the result
});

// try calling apiMethod 3 times, waiting 200 ms between each retry
async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
    // do something with the result
});

异步 GitHub 页面

async.retry 文档


31
投票

绝对不是要走的路 - while(!done);将进入硬循环并占用您所有的 cpu。

相反,你可以做这样的事情(未经测试,你可能想实施某种退避):

function tryUntilSuccess(options, callback) {
    var req = https.request(options, function(res) {
        var acc = "";
        res.on("data", function(msg) {
            acc += msg.toString("utf-8");
        });
        res.on("end", function() {
            var history = JSON.parse(acc);  //<== Protect this if you may not get JSON back
            if (history.success) {
                callback(null, history);
            } else {
                tryUntilSuccess(options, callback);
            }
        });
    });
    req.end();

    req.on('error', function(e) {
        // Decide what to do here
        // if error is recoverable
        //     tryUntilSuccess(options, callback);
        // else
        //     callback(e);
    });
}

// Use the standard callback pattern of err in first param, success in second
tryUntilSuccess(options, function(err, resp) {
    // Your code here...
});

13
投票

我发现德米特里使用 async 实用程序库 的答案非常有用,也是最好的答案。

This answer 将他的示例扩展为一个工作版本,该版本定义了

apiMethod
函数并向其传递了一个参数。我打算将代码添加为评论,但单独的答案更清楚。

const async = require('async');

const apiMethod = function(uri, callback) {
  try {
    // Call your api here (or whatever thing you want to do) and assign to result.
    const result = ...
    callback(null, result);
  } catch (err) {
    callback(err);
  }
};

const uri = 'http://www.test.com/api';

async.retry(
    { times: 5, interval: 200 },
    function (callback) { return apiMethod(uri, callback) },
    function(err, result) {
      if (err) {
        throw err; // Error still thrown after retrying N times, so rethrow.
      }
  });

重试文档:https://caolan.github.io/async/docs.html#retry

注意,在任务中调用

apiMethod(uri, callback)
的替代方法是使用
async.apply

async.retry(
        {times: 5, interval: 200},
        async.apply(task, dir),
        function(err, result) {
          if (err) {
            throw err; // Error still thrown after retrying N times, so rethrow.
          }
      });

我希望这能为某人提供一个好的复制/粘贴样板解决方案。


6
投票

这就是你想要做的吗?

var history = {};

function sendRequest(options, callback) {
    var req = https.request(options, function (res) {
        var acc = "";
        res.on("data", function (msg) {
            acc += msg.toString("utf-8");
        });
        res.on("end", function () {
            history = JSON.parse(acc);
            if (history.success) {
                callback(history);
            }
            else {
                sendRequest(options, callback);
            }
        });
    });
    req.end();
}

sendRequest(options, callback);

4
投票

不使用任何库..重试直到成功并且重试次数小于 11

  let retryCount = 0;
  let isDone = false;
  while (!isDone && retryCount < 10) {
    try {
      retryCount++;
      const response = await notion.pages.update(newPage);
      isDone = true;
    } catch (e) {
      console.log("Error: ", e.message);
      // condition for retrying
      if (e.code === APIErrorCode.RateLimited) {
        console.log(`retrying due to rate limit, retry count: ${retryCount}`);
      } else {
        // we don't want to retry
        isDone = true;
      }
    }
  }

3
投票

我已经使用 retry 模块解决了这个问题。

例子:

var retry = require('retry');

// configuration
var operation = retry.operation({
  retries: 2,           // try 1 time and retry 2 times if needed, total = 3
  minTimeout: 1 * 1000, // the number of milliseconds before starting the first retry
  maxTimeout: 3 * 1000  // the maximum number of milliseconds between two retries
});

// your unreliable task
var task = function(input, callback) {

  Math.random() > 0.5
    ? callback(null, 'ok')   // success
    : callback(new Error()); // error
}

// define a function that wraps our unreliable task into a fault tolerant task
function faultTolerantTask(input, callback) {

  operation.attempt(function(currentAttempt) {

    task(input, function(err, result) {

      console.log('Current attempt: ' + currentAttempt);

      if (operation.retry(err)) {  // retry if needed
          return;
      }

      callback(err ? operation.mainError() : null, result);
    });
  });
}

// test
faultTolerantTask('some input', function(err, result) {
  console.log(err, result);
});

1
投票

您可以尝试以下几行。我在写一个大概的想法,你应该用你的 HTTP 请求替换 trySomething。

function keepTrying(onSuccess) {
  function trySomething(onSuccess, onError) {
    if (Date.now() % 7 === 0) {
      process.nextTick(onSuccess);
    } else {
      process.nextTick(onError);
    }
  }
  trySomething(onSuccess, function () {
    console.log('Failed, retrying...');
    keepTrying(onSuccess);
  });
}

keepTrying(function () {
  console.log('Succeeded!');
});

我希望这有帮助。


1
投票

名为 Flashheart 的库也是一个合适的选择。这是一个易于使用并支持重试的休息客户端。

例如配置Flashheart重试10次,请求之间延迟500ms:

const client = require('flashheart').createClient({
  retries: 10,
  retryTimeout: 500
});

const url = "https://www.example.com/";
client.get(url, (err, body) => {
   if (err) {
      console.error('handle error: ', err);
      return;
   }
   console.log(body);
});

有关更多信息,请查看文档: https://github.com/bbc/flashheart

免责声明:我已经为这个图书馆做出了贡献。


0
投票
const INITIAL_DELAY = 2000
const MAX_ATTEMPTS = 10

function repeatUntilSucceeds(request) {
  return new Promise((resolve, reject) => {
    let attempt = 0
    let delay = INITIAL_DELAY

    function handleErrorRec(error) {
      if (attempt < MAX_ATTEMPTS) {
        setTimeout(execRequestRec, delay)
        attempt += 1
        delay *= 2
      } else {
        reject(error)
      }
    }

    function execRequestRec() {
      request().then(({ data, status, statusText }) => {
        if (status === 200) {
          resolve(data)
        } else {
          handleErrorRec(new Error(statusText))
        }
      }).catch(handleErrorRec)
    }

    execRequestRec()
  })
}

0
投票

写一个重试函数

function autoRetry(promiseFn, maxRetries = 3, delayMs = 1000) {
  return new Promise((resolve, reject) => {
    let retries = 0
    function attempt() {
      promiseFn()
        .then(resolve)
        .catch((error) => {
          retries++
          if (retries >= maxRetries) {
            reject(error)
          } else {
            setTimeout(attempt, delayMs)
          }
        })
    }
    attempt()
  })
}
© www.soinside.com 2019 - 2024. All rights reserved.