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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18581483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 15:29:13  来源:igfitidea点击:

How to do repeated requests until one succeeds without blocking in node?

javascriptnode.js

提问by Luka Horvat

I have a function that takes a parameter and a callback. It's supposed to do a request to a remote API and get some info based on the parameter. When it gets the info, it needs to send it to the callback. Now, the remote API sometimes fails to provide. I need my function to keep trying until it manages to do it and thencall the callback with the correct data.

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

Currently, I have the below code inside the function but I think that stuff like while (!done); isn't proper node code.

目前,我在函数中有以下代码,但我认为像 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);

How do I do it the right way?

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

回答by Dmitry Matveev

There is no need to re-invent the wheel... you can use a popular async utility library, 'retry' method in this case.

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

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

async GitHub page

异步 GitHub 页面

async.retry docs

async.retry 文档

回答by dc5

Definitely not the way to go - while(!done); will go into a hard loop and take up all of your cpu.

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

Instead you could do something like this (untested and you may want to implement a back-off of some sort):

相反,您可以执行以下操作(未经测试,您可能希望实现某种退避):

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

回答by Dylan Hogg

I found Dmitry's answer using the async utility libraryvery useful and the best answer.

我发现 Dmitry 使用async 实用程序库的答案非常有用,并且是最佳答案。

This answer expands his example to a working version that defines the apiMethodfunction and passes it a parameter. I was going to add the code as a comment but a separate answer is clearer.

这个答案将他的例子扩展到一个定义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.
      }
  });

Retry documentation: https://caolan.github.io/async/docs.html#retry

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

Note, an alternative to calling apiMethod(uri, callback)in the task is to use async.apply:

请注意,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.
          }
      });

I hope this provides a good copy/paste boiler plate solution for someone.

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

回答by Joon

Is this what you are trying to do?

这是你想要做的吗?

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

回答by Zanon

I've solved this problem using the retrymodule.

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

Example:

例子:

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

回答by nickool

A library called Flashheartis also a suitable alternative. It's a rest client designed to be easy to use and supports retries.

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

For example, configure Flashheart to retry 10 times, with a delay of 500ms between requests:

例如,将 Flashheart 配置为重试 10 次,请求之间的延迟为 500 毫秒:

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

For further information, check out the docs: https://github.com/bbc/flashheart

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

Disclaimer: I have contributed to this library.

免责声明:我为这个库做出了贡献。

回答by idrarig

You could try something along the following lines. I'm writing a general idea, you should replace trySomething with your HTTP request.

您可以尝试以下方法。我正在写一个总体思路,您应该用您的 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!');
});

I hope this helps.

我希望这有帮助。