我有一些使用Breeze保存数据的代码,并报告了多次保存的进度,这些保存运行得相当好。 但是,有时保存会超时,我想自动重试一次。 (目前用户显示错误,必须手动重试) 我正在努力找到一个合适的方法来做到这一点,但我对承诺感到困惑,所以我很感激一些帮助。 这是我的代码:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
修改 澄清为什么我一直在尝试这个: 我有一个http拦截器,它在所有http请求上设置超时。当请求超时时,向上调整超时,会向用户显示一条错误消息,告诉他们如果愿意,可以重试更长时间。
在一个http请求中发送所有更改看起来可能需要几分钟,因此我决定将更改分解为多个http请求,并在每个请求成功时报告进度。
现在,批处理中的某些请求可能会超时,有些可能不会。
然后我明白我会为http请求设置一个低超时并自动增加它。但批处理是以相同的超时设置异步发送的,并且会针对每个故障调整时间。那不好。
要解决此问题,我想在批处理完成后移动超时调整,然后重试所有请求。
说实话,我不确定自动超时调整和重试是一个好主意。即使是这样,在一个接一个地发出http请求的情况下可能会更好 - 我也一直在关注:https://stackoverflow.com/a/25730751/150342
答案 0 :(得分:2)
在$q.all()
下游编排重试是可能的,但确实会非常混乱。在汇总承诺之前执行重试要简单得多。
您可以利用闭包和重试计数器,但构建捕获链更清晰:
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
现在,修改你的for循环:
retry()
。retry().then(...)
返回的承诺推送到promises
阵列。var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
修改强>
目前尚不清楚为什么要重试$q.all()
的缩减。如果在重试之前引入一些延迟,最简单的方法就是在上面的模式中做。
但是,如果重试$q.all()
的下游是一个确定的要求,这里是一个干净的递归解决方案,允许任意数量的重试,对外部变量的需求最小:
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
进度报告在这里略有不同。有了更多的想法,它可以更像你自己的答案。
答案 1 :(得分:1)
这是如何解决它的一个非常粗略的想法。
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
答案 2 :(得分:0)
这里有两个有用的答案,但经过这一切,我得出的结论是,立即重试并不适合我。
我想等待第一批完成,然后如果失败是由于超时,请在重试失败之前增加超时限额。 所以我拿了Juan Stiza的例子并修改它来做我想做的事。即使用$ q.all重试失败
我的代码现在看起来像这样:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});