我有一个数组queue
,我会在修改它们时将对象推送到它。如果用户按下save
,那么我将遍历queue
并为其应用相应的API调用。
如果API调用成功完成,我想从queue
中删除该项,否则将其保留在内部并通知用户某些项目未成功保存。我目前有这个(在AngularJS中)
var unsuccessfulItems = [];
var promise = queue.map(function(item) {
var defer = $q.defer();
myCallFunction( item
, function( response ) {} // Success
, function( response ) { // Error
unsuccessfulItems.push(item);
}
)
defer.resolve();
return defer.promise;
})
// Once all items have been processed
$q.all( promise ).then( function() {
queue = unsuccessfulItems;
});
有更好的方法吗?
答案 0 :(得分:1)
你已经在使用promises,你可能想要端到端地做。而且,你过早地解决了这个承诺。
假设你不希望宣传myCallFunction
本身的次优案例,你仍然应该宣传它。
function myCall(item){
var d = $q.defer();
myCallFunction(item,function(r){ d.resolve({val:r,item:item});}
,function(r){ d.reject(r);});
return d.promise;
}
注意,我们正在解决异步函数完成之后的延迟,而不是之前。
现在,我们需要实现一个“结算”功能,无论如何都会在所有承诺完成时解决。这就像$q.all
,但会等待所有承诺解决而不履行。
function settle(promises){
var d = $q.defer();
var counter = 0;
var results = Array(promises.length);
promises.forEach(function(p,i){
p.then(function(v){ // add as fulfilled
results[i] = {state:"fulfilled", promise : p, value: v};
}).catch(function(r){ // add as rejected
results[i] = {state:"rejected", promise : p, reason: r};
}).finally(function(){ // when any promises resolved or failed
counter++; // notify the counter
if (counter === promises.length) {
d.resolve(results); // resolve the deferred.
}
});
});
}
这种结算函数存在于大多数承诺实现中,但不存在于$q
中。我们也可以通过拒绝和$q.all
完成此操作,但这意味着流量控制的例外,这是一种不好的做法。
现在,我们可以settle
:
settle(queue.map(myCall)).then(function(results){
var failed = results.filter(function(r){ return r.state === "rejected"; });
var failedItems = failed.map(function(i){ return i.value.item; });
});
答案 1 :(得分:-1)
这是一个简洁的解决方案,可以解决非常有限的$ q的限制,而无需使用笨重的函数/ polyfill来扩充其方法。
特别是
.all()
方法但不是allSettled()
。 我在这里使用的技巧是:
$q.all()
的行为与失踪的$q.allSettled()
相同。function saveQueue() {
//First some safety
if(queue.saving) {
return $q.defer().resolve(-1).promise;
}
queue.saving = true;
var settled = [],//the sole purpose of this array is to allow $q.all() to be called. All promises place in this array will be resolved.
successes = [];//an array to be (sparsely) populated with `true` for every item successfully saved. This helps overcome the lack of a simple test of a $q promise's state (pending/fulfilled/rejected).
queue.forEach(function(item, i) {
var defer = $q.defer();
settled[i] = defer.promise;
myCallFunction(item, function(response) {
//here do awesome stuff with the response
//`item`, if required, is in scope
successes[i] = true;//register the promise's success
defer.resolve();//as you would expect
}, function(error) {
//here do awesome stuff with the error (eg log it).
//`item`, if required, is in scope
defer.resolve();//here we *resolve*, not reject, thus allowing `$q.all(settled)` to reflect the settling of all promises regardless of whether they were fulfilled or rejected.
});
});
// Once all items have been processed
return $q.all(settled).then(function() {
queue = queue.filter(function(val, i) {
return !successes[i];
});
queue.saving = false;
return queue.length;
});
}
saveQueue()
将返回:
saveQueue()
仍在进行中,则承诺为-1,或纯粹主义者无疑会认为这个解决方案是一个“反模式”(yuk!),因为需要解决成功和错误的承诺,但问题的性质和$ q的局限性鼓励我们朝这个方向发展。
除此之外,您可能还需要一种机制来确保放置在队列中的项目是唯一的。重复是最浪费的,最坏的情况可能会导致错误。