我有一个接受参数和回调的函数。它应该向远程 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);
如何以正确的方式做到这一点?
无需重新发明轮子......您可以使用流行的异步实用程序库,在这种情况下为“重试”方法。
// 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
});
绝对不是要走的路 - 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...
});
我发现德米特里使用 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.
}
});
我希望这能为某人提供一个好的复制/粘贴样板解决方案。
这就是你想要做的吗?
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);
不使用任何库..重试直到成功并且重试次数小于 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;
}
}
}
我已经使用 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);
});
您可以尝试以下几行。我在写一个大概的想法,你应该用你的 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!');
});
我希望这有帮助。
名为 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
免责声明:我已经为这个图书馆做出了贡献。
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()
})
}
写一个重试函数
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()
})
}