2017-05-01 2 views
0
Iterieren

Ich habe folgendes:Javascript forEach - für SetTimeout warten, bevor sie abgeschlossen

https://jsfiddle.net/qofbvuvs/

var allItems = ["1", "2", "3"] 
var allPeople = ["A", "B"] 

var testFoo = function(itemValue, peopleValue) { 
    setTimeout(function(){ 
     return itemValue == "3" && peopleValue == "B" 
    }, 200) 
} 

allItems.forEach(function(itemValue) { 
    allPeople.forEach(function(peopleValue) { 
    // I want to iterate through each object, completing testFoo before moving on to the next object, without recursion. TestFoo has a few instances of setTimeout. I've tried using promises to no avail. 
    if (testFoo(itemValue, peopleValue)){ 
     alert("success") 
    } else{ 
     // nothing 
    }; 
    }) 
}) 
alert("complete") 

Mein Ziel ist es, durch jedes Element zu durchlaufen, eins nach dem anderen, um, während für die Warte Ergebnisse von testFoo. Wenn testFoo besteht, sollte ich die Ausführung stoppen.

Ich habe versucht, Versprechen (https://jsfiddle.net/qofbvuvs/2/) zu verwenden, aber kann nicht das Verhalten, das ich suche. Success sollte vor Complete aufgerufen werden. TestFoo hat ein paar setTimeouts, die ich umgehen muss (es ist eine Bibliothek, die ich nicht ändern kann). Wie kann dies erreicht werden?

+0

seit dem Aufruf von testFoo() führt ab sofort (ohne für den Timeout zu warten) wird es keinen Rückgabewert wie Sie haben‘ erwarten. Wenn Sie async verwenden, müssen Sie sich daran gewöhnen, keine Rückgabe zu verwenden und Ergebnisse in Callbacks zu behandeln. – James

+0

@James Ich habe ein Deferred in 'testFoo' eingeführt, um das Ergebnis zu übergeben, aber die Schleifen werden fortgesetzt, ohne auf das Ergebnis zu warten. Irgendwie kann ich zurückgestellte Callbacks nutzen und die Loops warten lassen? – SB2055

+0

@James mein Versuch: https://jsfiddle.net/qofbvuvs/2/ – SB2055

Antwort

1

Eine Möglichkeit, die Sie tun können, ist es durch deferreds jQuery und manuell Schreiten durch Ihre Arrays, anstatt die in Schleifen gebaut verwenden, so können Sie steuern, ob/wann Sie fortfahren. Ich schätze, es benutzt die Rekursion in gewisser Weise, aber zu nichts anderem als zum Aufrufen der nächsten Iteration - es gibt keinen verrückten rekursiven Rückgabewert, der sich entwirrt oder irgendetwas, das Rekursion komplex macht. Lassen Sie mich wissen, ob dies für Sie arbeitet:

var allItems = ["1", "2", "3"] 
 
var allPeople = ["A", "B"] 
 

 
var testFoo = function(itemValue, peopleValue) { 
 
    var deferredObject = $.Deferred(); 
 
    setTimeout(function() { 
 
    deferredObject.resolve(itemValue == "3" && peopleValue == "B") 
 
    }, 200) 
 
    return deferredObject; 
 
} 
 
var currentItemIndex = 0; 
 
var currentPeopleIndex = 0; 
 

 
var testDeferred = $.Deferred(); 
 

 
function testAll() { 
 
    testFoo(allItems[currentItemIndex], allPeople[currentPeopleIndex]).done(function(result) { 
 
    if (result) { 
 
     // found result - stop execution 
 
     testDeferred.resolve("success"); 
 
    } else { 
 
     currentPeopleIndex++; 
 
     if (currentPeopleIndex >= allPeople.length) { 
 
     currentPeopleIndex = 0; 
 
     currentItemIndex++; 
 
     } 
 
     if (currentItemIndex >= allItems.length) { 
 
     // result not found - stop execution 
 
     testDeferred.resolve("fail"); 
 
     } else { 
 
     // check next value pair 
 
     testAll(); 
 
     } 
 
    } 
 
    }); 
 
    return testDeferred; 
 
} 
 

 
testAll().done(function resolveCallback (message) { 
 
    alert(message); 
 
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

+0

Was sind Ihre Gedanken zum async/have Ansatz unten? – SB2055

+1

@ SB2055 Nun, es hängt von der Umgebung ab, in der Sie das ausführen werden. Async/await ist nicht einmal eine ES6-Funktion - es ist eigentlich ein Teil der ES2016 + -Spezifikation. Es hat ordentliche Unterstützung für die neuesten Versionen der wichtigsten Browser, aber selbst ein Browser, der nicht mehr aktuell ist, wird daran ersticken. Du könntest einen Transpiler benutzen, wie Sasang erwähnt hat, aber momentan haben sie noch schlimmere Unterstützung für asynchrone Funktionen. Das Schöne an der jQuery-Implementierung von verzögerten Objekten ist, dass sie Polyfills/Fallbacks und die Unterstützung von Legacy-Browsern enthält, sodass Sie sich nie darum kümmern müssen. – mhodges

+0

@ SB2055 Ich würde sagen, in ungefähr einem Jahr wird der async/have-Ansatz viel bessere Unterstützung haben und viel weiter verbreitet sein. Bis dahin bleibe bei Features, die unterstützt werden, imo. [Hier ist die Kompatibilitätstabelle für asynchrone Funktionen] (http://kangax.github.io/compat-table/es2016plus/#test-async_functions) – mhodges

0

Verwendet Promise.all, um Ihr Problem zu lösen. Hoffe das hilft.

var allItems = ["1", "2", "3"]; 
 
var allPeople = ["A", "B"]; 
 

 
var testFoo = function(itemValue, peopleValue) { 
 
    return new Promise(function(resolve) { 
 
    setTimeout(function() { 
 
     resolve(itemValue === "3" && peopleValue === "B"); 
 
    }, 200); 
 
    }); 
 
} 
 

 
var promises = []; 
 
allItems.forEach(function(itemValue) { 
 
    allPeople.forEach(function(peopleValue) { 
 
    promises.push(testFoo(itemValue, peopleValue).then(function(result) { 
 
     console.log(result?'success':'fail'); 
 
     return result; 
 
    })); 
 
    }) 
 
}); 
 

 
Promise.all(promises).then(function(results) { 
 
    console.log('Result from all promise: ' + results); 
 
    console.log("complete"); 
 
});

0

Wenn Sie eine Kette von Versprechungen so zu konstruieren, wo jedes Versprechen nicht erstellt wird, bis es Vorgänger Versprechen in der Kette löst, sollten Sie eine Reihe von Funktionen erstellen, die gib deine Versprechen zurück und nutze die Reduktion, um die benötigte Versprechenskette zu schaffen.

var allItems = ["1", "2", "3"]; 
 
var allPeople = ["A", "B"]; 
 

 
// Async requests aren't made once we reach "2" and "B" 
 
var resultToStopOn = function(itemValue, peopleValue) { 
 
    return itemValue === "2" && peopleValue === "B" 
 
} 
 

 
var testFoo = function(itemValue, peopleValue) { 
 
    return new Promise(function(resolve) { 
 
    console.log('Starting promise for: ', itemValue, peopleValue); 
 
    setTimeout(function() { 
 
     console.log('Resolving promise for: ', itemValue, peopleValue); 
 
     resolve(resultToStopOn(itemValue, peopleValue)); 
 
    }, 200); 
 
    }); 
 
} 
 

 
var functionsThatReturnPromises = []; 
 
allItems.forEach(function(itemValue) { 
 
    allPeople.forEach(function(peopleValue) { 
 
    functionsThatReturnPromises.push(function() { 
 
     return testFoo(itemValue, peopleValue); 
 
    }); 
 
    }); 
 
}); 
 

 
functionsThatReturnPromises 
 
    .reduce(function(chainOfPromises, fnReturningAPromise) { 
 
    return chainOfPromises.then(function(result) { 
 
     // if result is false, we continue down the chain 
 
     // otherwise we just propagate the result and don't chain anymore 
 
     return !result 
 
     ? fnReturningAPromise() 
 
     : result; 
 
    }); 
 
    }, Promise.resolve()) 
 
    .then((result) => { 
 
    console.log('Result: ', result); 
 
    });

1

Da es bereits eine Reihe von Lösungen aus ist, ich dachte, ich mit async/await eine Lösung hinzufügen würde. Ich persönlich mag dieses, weil es den Code ähnlich wie Ihr Original und kompakter hält, wo Sie keine Zwischenzustände verfolgen oder alle Versprechen lösen müssen. Die Lösung besteht darin, die for-Schleifen innerhalb einer asynchronen Funktion zu umbrechen und forEach in eine Basis für (var x ...) zu ändern, da wait nicht innerhalb einer forEach-Anweisung korrekt arbeitet, da Sie eine innere nicht asynchrone Funktion definieren und innerhalb von await aufrufen das ist ungültig. Ändern Sie außerdem die testFoo Funktion, um eine Zusage zurückzugeben.

Da sich die for-Schleifen innerhalb einer Funktion befinden, können Sie sie einfach beenden, sobald eine Übereinstimmung gefunden wurde, um weitere Überprüfungen zu überspringen. Ich fügte auch eine Rückkehranweisung am Ende der Schleifen hinzu, um anzuzeigen, dass nichts gefunden wurde, was praktisch sein kann.

Schließlich, da Async-Funktionen selbst ein Versprechen zurückgeben, müssen Sie nur die letzten Warnungen innerhalb der then des zurückgegebenen Versprechens hinzufügen, um zu bewerten, ob eine Übereinstimmung gefunden wurde oder nicht.

async/erwarten Beispiel:

var allItems = ["1", "2", "3"] 
 
var allPeople = ["A", "B"] 
 

 
var testFoo = function(itemValue, peopleValue) { 
 
    return new Promise(function(resolve, reject){ 
 
    setTimeout(function(){ 
 
     resolve(itemValue == "3" && peopleValue == "B"); 
 
    }, 200) 
 
    }); 
 
} 
 

 
async function checkItemPeople(){ 
 
    for(var itm=0; itm<allItems.length; itm++){ 
 
    for(var ppl=0; ppl<allPeople.length; ppl++){ 
 
     var result = await testFoo(allItems[itm], allPeople[ppl]); 
 
     if (result) { 
 
     alert("success"); 
 
     return true; 
 
     } else { 
 
     // nothing 
 
     }; 
 
    } 
 
    } 
 
    return false; 
 
} 
 

 
checkItemPeople().then(function(resp){ 
 
    resp ? alert("Found Something") : alert("Theres nothing"); 
 
});

+0

Dies ist mein Favorit, wenn es funktioniert - ich werde es testen und als markieren vervollständigen, wenn es das tut. Ich frage mich, warum niemand anderes das empfohlen hat? – SB2055

+1

Ich denke, dass etwas davon mit der Umgebung zu tun haben könnte, auf der Sie es ausführen werden. Wenn async/await nicht nativ unterstützt wird, müssen Sie es transpilieren, damit Arbeit hinzugefügt werden kann. – Sasang

+0

@Sasang Babel, typescript, core-js, traceur, etc. unterstützen nur 3 von 15 async/erwarten Funktionen, so (zu diesem aktuellen Zeitpunkt) selbst ein transpiler ist keine sichere Antwort. – mhodges

Verwandte Themen