2014-04-26 6 views
6

Ich habe ein Array queue, dass ich Objekte an sie schieben, wenn sie geändert werden. Wenn der Benutzer save drückt, werde ich die queue Schleife durchlaufen und den entsprechenden API-Aufruf für sie anwenden.JavaScript .map auf ein Array und Entfernen von Elementen, wenn die Bedingung erfüllt

Wenn der API-Aufruf erfolgreich ausgeführt wird, möchte ich das Element aus dem queue entfernen, andernfalls behalten Sie es im Inneren und benachrichtigen Sie den Benutzer, dass einige Elemente nicht erfolgreich gespeichert wurden. Ich habe dies derzeit (in 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; 
}); 

Gibt es eine bessere Möglichkeit, dies zu tun?

+2

Scheint, dass 'filter' erforderlich ist: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter? –

Antwort

1

Sie verwenden bereits Versprechungen, möglicherweise möchten Sie es Ende-zu-Ende tun. Außerdem lösen Sie das Versprechen zu früh auf.

Angenommen, der suboptimale Fall, in dem Sie nicht selbst promi- tieren möchten, sollte Sie dennoch promi- tifizieren.

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; 
} 

Hinweis, aufgelöst wir die Zurückstellungs nach die asynchrone Funktion durchgeführt wird, bevor es nicht.

Jetzt müssen wir eine "Settle" -Funktion implementieren, die löst, wenn alle Versprechen gemacht werden, egal was passiert. Dies ist wie $q.all aber wird auf alle Versprechen warten, um zu lösen und nicht zu erfüllen.

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. 
      } 
     }); 
    }); 
} 

Diese Art von settle Funktion besteht in den meisten Versprechen Implementierungen aber nicht in $q. Wir hätten dies auch mit Ablehnungen und $q.all tun können, aber das würde Ausnahmen für die Flusskontrolle bedeuten, was eine schlechte Praxis ist.

Jetzt können wir 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

Hier ist eine kurze Lösung, die rund um die Grenzen des sehr begrenzten $ q funktioniert, ohne die Notwendigkeit, ihre Methoden mit sperrigen Funktionen/polyfills zu erweitern.

Insbesondere

  • $ q Versprechen enthalten keinen einfachen Mechanismus zum Abfragen ihren Status
  • $ q hat eine .all() Methode aber nicht allSettled().

Die Tricks, die ich hier beschäftigen, sind:

  • Versprechen in einem Array zu halten und ein Register über ihre (eventuellen) Erfolg in einem kongruenten zweiten Array
  • verspricht sowohl auf Erfolg und Misserfolg zu lösen , so dass sich $q.all() wie das fehlende $q.allSettled() verhalten kann.
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() zurückkehren wird:

  • ein Versprechen von -1, wenn eine vorherige saveQueue() noch im Gange ist, oder
  • ein Versprechen der Warteschlangenlänge schließlich beglichen spart.

Puristen werden erachten zweifellos diese Lösung eine „Antipattern“ (igitt!) Aufgrund der Notwendigkeit, verspricht sowohl Erfolg und Fehler zu beheben, aber die Art des Problems und die Grenzen von $ q uns in dieser Richtung fördern .

Darüber hinaus benötigen Sie wahrscheinlich einen Mechanismus, um sicherzustellen, dass in der Warteschlange platzierte Elemente eindeutig sind. Duplikate sind bestenfalls verschwenderisch und können im schlimmsten Fall zu Fehlern führen.

+0

Warum der Downvote? Erklärung bitte! –