JavaScript .map on an array and removing items if condition satisfied

前端 未结 2 498
日久生厌
日久生厌 2020-12-11 05:36

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

相关标签:
2条回答
  • 2020-12-11 06:20

    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,

    • $q's promises don't include a simple mechanism for enquiring their status
    • $q has an .all() method but not allSettled().

    The tricks I employ here are :

    • to keep promises in one array and a record of their (eventual) success in a congruent second array
    • to resolve promises on both success and failure, thus allowing $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 :

    • a promise of -1 if a previous saveQueue() is still in progress, or
    • a promise of the queue length after all saves are settled.

    Purists 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.

    0 讨论(0)
  • 2020-12-11 06:23

    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; });
     });
    
    0 讨论(0)
提交回复
热议问题