I have an array queue
that I push objects to it when they are modified. If the user presses save
, then I will loop through the queue
a
Here's a concise solution that works around the limitations of the very limited $q, without the need to augment its methods with bulky functions/polyfills.
In particular,
.all()
method but not allSettled()
. The tricks I employ here are :
$q.all()
to behave like the missing $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()
will return :
saveQueue()
is still in progress, orPurists will undoubtedly deem this solution an "antipattern" (yuk!) due to the need to resolve promises on both success and error, however the nature of the problem and the limitations of $q encourage us in this direction.
In addition to all this, you will probably need a mechanism to ensure that items placed in the queue are unique. Duplicates would be at best wasteful, and at worst may cause errors.
You're already using promises, you might want to do it end-to-end. Also, you're resolving the promise too early.
Assuming the suboptimal case where you don't want to promisify myCallFunction
itself, you should still promisify it.
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;
}
Note, we are resolving the defer after the asynchronous function is done, not before it.
Now, we need to implement a "Settle" function, that resolves when all promises are done no matter what. This is like $q.all
but will wait for all promises to resolve and not fulfill.
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.
}
});
});
}
This sort of settle function exists in most promise implementations but not in $q
. We could have also done this with rejections and $q.all
, but that would mean exceptions for flow control which is a bad practice.
Now, we can 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; });
});