Vänta på forEach (Javascript)

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Feb 2005

Vänta på forEach (Javascript)

Hej!

Jag försöker greppa mig kring detta.

Jag försöker uppdatera en array med ny data.

Men den lyckas uppdatera localStorage med "fel array". Jag vill att den går igenom en forEach loop först och sedan när den är klar skall den lägga den uppdaterade arrayn som localstorage.

Hur använder man t.ex. Promises för med en forEach eller någon annan metod att vänta på min forEach att bli klar.

Exempel på hur detta kan se ut.

updateSomething(){ var newArr = JSON.parse(localStorage.getItem('theData')) newArr.forEach((element)=>{ return this.updateData(element); //funktion som uppdaterar datan och return elementet }) console.log(newArr) //Detta visar korrekt data localStorage.setItem('theData', JSON.parse(newArr)); // detta visar gamla datan }

Gaemer Addicted!

Trädvy Permalänk
Medlem
Registrerad
Jul 2017

Det är inte möjligt att vänta på en foreach sådär.

Det du får göra är att fuska lite med en rekursiv funktion som anropas när ditt callback är färdigt.

Likt det här:
https://jsfiddle.net/rrp0kdbw/1/

Vill du jobba med promises så är det bara att de returnerar varandra såklart.

EDIT:

let arr = [1, 2, 3, 4, 5]; let $numbers = document.getElementById('numbers'); (function nextNumber(numbers) { if (numbers.length > 0) { let nr = numbers.shift(); setTimeout(() => { let $div = document.createElement('div'); $div.append(nr); $numbers.append($div); nextNumber(numbers); }, 500); } else { // done $numbers.append('Done'); } }(arr));

Uppdaterade även koden i jsfiddle med kommentarer:
https://jsfiddle.net/rrp0kdbw/2/

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Feb 2005
Skrivet av JeanC:

Det är inte möjligt att vänta på en foreach sådär.

Det du får göra är att fuska lite med en rekursiv funktion som anropas när ditt callback är färdigt.

Likt det här:
https://jsfiddle.net/rrp0kdbw/1/

Vill du jobba med promises så är det bara att de returnerar varandra såklart.

EDIT:

let arr = [1, 2, 3, 4, 5]; let $numbers = document.getElementById('numbers'); (function nextNumber(numbers) { if (numbers.length > 0) { let nr = numbers.shift(); setTimeout(() => { let $div = document.createElement('div'); $div.append(nr); $numbers.append($div); nextNumber(numbers); }, 500); } else { // done $numbers.append('Done'); } }(arr));

Uppdaterade även koden i jsfiddle med kommentarer:
https://jsfiddle.net/rrp0kdbw/2/

Tack så mycket!

Det där ser inte omöjligt ut alls.

Vet du alls hur det skulle se ut med promises? Det känns som det skulle vara kul att lära sig och förmodligen blir det mer "läsbart". Men om inte tack för svaret!

Gaemer Addicted!

Trädvy Permalänk
Entusiast
Testpilot
Plats
Chalmers
Registrerad
Aug 2011
Skrivet av we_man1:

updateSomething(){ var newArr = JSON.parse(localStorage.getItem('theData')) newArr.forEach((element)=>{ return this.updateData(element); //funktion som uppdaterar datan och return elementet }) console.log(newArr) //Detta visar korrekt data localStorage.setItem('theData', JSON.parse(newArr)); // detta visar gamla datan }

Borde inte sista raden vara:

localStorage.setItem('theData', JSON.stringify(newArr);

Sen undrar jag om du inte kan göra uppdateringen mindre imperativt och mer deklarativt?

const KEY = 'theData'; function updateData(data) { return data; // identity update } const oldData = JSON.parse(localStorage.getItem(KEY)); const newData = oldData.map(updateData); localStorage.setItem(KEY, JSON.stringify(newData));

5930K • Corsair DP 32 GiB • EVGA GTX 980 • 2x Swift PG278Q
Better SweClockersDisplayPort över USB-C

Köp processor för framtiden™, men inte grafikkort.

Trädvy Permalänk
Medlem
Registrerad
Jul 2017

@we_man1: Såhär

https://jsfiddle.net/g0L2acvx/

function display(str) { let $numbers = document.getElementById('numbers'); let $div = document.createElement('div'); $div.append(str); $numbers.append($div); } function runAll(numbers) { if (numbers.length > 0) { return new Promise((resolve, reject) => { let nr = numbers.shift(); display(nr); setTimeout(() => resolve(), 500); }).then(() => runAll(numbers)); } else { return Promise.resolve(); } } let numbers = [0, 1, 2, 3, 4, 5]; return runAll(numbers).then(() => { alert('Klart'); });

Hoppas det går lättare att läsa; så har du reject där med ifall du vill säga att något blev fel.

Värt att poängtera att har du stora arrayer så är det onödigt att leta efter diven med "numbers" varje gång; hade lyft ut den i stället.

Sedan ska du ha stringify precis som @Alling skrev.

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Feb 2005
Skrivet av JeanC:

@we_man1: Såhär

https://jsfiddle.net/g0L2acvx/

function display(str) { let $numbers = document.getElementById('numbers'); let $div = document.createElement('div'); $div.append(str); $numbers.append($div); } function runAll(numbers) { if (numbers.length > 0) { return new Promise((resolve, reject) => { let nr = numbers.shift(); display(nr); setTimeout(() => resolve(), 500); }).then(() => runAll(numbers)); } else { return Promise.resolve(); } } let numbers = [0, 1, 2, 3, 4, 5]; return runAll(numbers).then(() => { alert('Klart'); });

Hoppas det går lättare att läsa; så har du reject där med ifall du vill säga att något blev fel.

Värt att poängtera att har du stora arrayer så är det onödigt att leta efter diven med "numbers" varje gång; hade lyft ut den i stället.

Sedan ska du ha stringify precis som @Alling skrev.

Tack för svaren! De har hjälpt. Och ja jag skrev bara ett exempel snabbt så blev visst fel.

En sista fråga, annars har jag börjat greppa detta nu. Kan man lägga reject utan en if statement.. t.ex. Om ditt nummer array kommer från en api, och den av någon anledning inte är tillgänlig?

Gaemer Addicted!

Trädvy Permalänk
Medlem
Registrerad
Jul 2017

@we_man1: Du kan köra reject när som helst; Promise tar hand om det och ignorerar allt efter du kört en promise ända ner till en catch eller om du har det i en metod fast som parameter nr2.

Trädvy Permalänk
Medlem
Plats
Borlänge
Registrerad
Jul 2001

Varför göra det så komplicerat? En vanlig for loop är inte asynk.

for(var i = 0; i < newArr.length; i++) this.updateData(element);

Men vem vet, jag kanske är för old school och vill ha enklaste lösning

"May God stand between you and harm in all the empty places you must walk"

Trädvy Permalänk
Medlem
Plats
#Archlinux
Registrerad
Jun 2007
Skrivet av thrawn:

Varför göra det så komplicerat? En vanlig for loop är inte asynk.

for(var i = 0; i < newArr.length; i++) this.updateData(element);

Men vem vet, jag kanske är för old school och vill ha enklaste lösning

Tänkte precis samma. Varför hålla på med async i detta fallet då loopen kommer köras utan större vänttider, en annan grej om man gör get calls eller dylikt som faktiskt tar tid att få respons mm.

Arch - Makepkg, not war -||- Asus Crosshair Hero VI -||- GSkill 16GiB DDR4 15-15-15-35-1T 3600Mhz -||- AMD 1600x @ 4.1GHz -||- nVidia MSI 970 Gaming -||- Samsung 850 Pro -||- EVEGA G2 750W -||- Corsair 570x -||- Asus Xonar Essence STX -||- Sennheiser HD-650 -||
Arch Linux, one hell of a distribution.

Trädvy Permalänk
Medlem
Plats
Malmö
Registrerad
Feb 2006

.forEach är inte async, allt som händer i den kommer hända innan någon rad efter exekverar.

const numbers = [1, 2, 3, 4, 5]; numbers.forEach((number) => console.log(number)); console.log("done");

Kodsnutten kommer alltid skriva
1
2
3
4
5
done

Du sparar datan fel och läser nog in något annat eller så. Eller så skriver du över den någon annan stans

Trädvy Permalänk
Medlem
Plats
Borlänge
Registrerad
Jul 2001

jag tyckte det var skumt att forEach var async, men jag brukar inte använda den pga kompatibilitet. Så jag hade egentligen ingen koll på just den, tyckte bara föreslagna förslag var onödigt komplicerade

@we_man1: vad för updateData ? Gör den något async?

Skrivet av Killbom:

.forEach är inte async, allt som händer i den kommer hända innan någon rad efter exekverar.

const numbers = [1, 2, 3, 4, 5]; numbers.forEach((number) => console.log(number)); console.log("done");

Kodsnutten kommer alltid skriva
1
2
3
4
5
done

Du sparar datan fel och läser nog in något annat eller så. Eller så skriver du över den någon annan stans

Skickades från m.sweclockers.com

"May God stand between you and harm in all the empty places you must walk"

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Feb 2005
Skrivet av thrawn:

jag tyckte det var skumt att forEach var async, men jag brukar inte använda den pga kompatibilitet. Så jag hade egentligen ingen koll på just den, tyckte bara föreslagna förslag var onödigt komplicerade

@we_man1: vad för updateData ? Gör den något async?

Skickades från m.sweclockers.com

Hej!

Med mitt fall så kommer "updateData" funktionen hämta data från en api. Vilket gör att det pajar för sig själv. Men om du har någon enkel lösning hur man får den att vänta på responsen så tar jag gärna emot tips

Gaemer Addicted!

Trädvy Permalänk
Medlem
Plats
Borlänge
Registrerad
Jul 2001

@we_man1: Yes det har jag

Såhär brukar jag göra, it ain't pretty but it works

Har bara skrivit den här koden på frihand, men det är bara för att du ska få lite koll på logiken. Så får du skriva om den själv. Det går självklart bra att använda promises, det är väl egentligen att föredra, men jag kan inte den syntaxen i huvudet...

function fetchData(onDone){ //Hämta data och när den är hämtad så kör functionen nedan. Det går självklart att använda promises istället, det här är bara för att jag ska skriva det lite snabbare :) if(typeof onDone === "function") onDone(result) } var arr = [1,2,3,4], fetchCounter = arr.length; //håll koll på hur många requests som kommer att göras for(var i = 0; i < arr.length; i++) { fetchData(arr[i], function(res){ fetchCounter--;//här har vi fått svar på en request, så då drar vi bort en från vår variabel som håller koll på det if(fetchCounter < 1) { //Här har alla requests fått svar och är behandlade localStorage.setItem('blah'); } }); }

"May God stand between you and harm in all the empty places you must walk"

Trädvy Permalänk
Medlem
Registrerad
Jun 2017

Vad sägs om att göra en funktion som heter updateDataArray() som tar hela arrayen av data istället för bara ett element i taget, sedan när den är klar returnerar den en Promise. Då behöver du bara hantera det såhär:

const oldArr = JSON.parse(localStorage.getItem('theData')) updateDataArray(oldArr).then((resultArr) => { localStorage.setItem('theData', JSON.stringify(resultArr)); }.catch((err) => { // Något blev fel });

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Sep 2008

Kanske sent svar men det låter som du behöver async/await, tror det kom med es6. Kolla gärna vidare då det hanterar asynchrona kallelser utan att behöva egna callbacks. Jag har använt det vis utveckling av react-native när mobilen har behövt accessa localstorage

Skickades från m.sweclockers.com

Trädvy Permalänk
Medlem
Registrerad
Jun 2017
Skrivet av Razki:

Kanske sent svar men det låter som du behöver async/await, tror det kom med es6. Kolla gärna vidare då det hanterar asynchrona kallelser utan att behöva egna callbacks. Jag har använt det vis utveckling av react-native när mobilen har behövt accessa localstorage

Skickades från m.sweclockers.com

Async är dock en del av nästa standard vill jag minnas. I så fall får man använda något som Babel.

Trädvy Permalänk
Medlem
Plats
#Archlinux
Registrerad
Jun 2007
Skrivet av MrDoggo:

Async är dock en del av nästa standard vill jag minnas. I så fall får man använda något som Babel.

Dock så har du promises som kan användas redan idag. Finns miljontals exempel på nätet både med och utan Jquery.

Nu är frågan om du hämtar något via ett API gör du ett stort call eller flera små? Värt är att webbläsaren har en viss limit på hur många olika calls den kan göra. Dock gör du flera calls i en loop så bör du bryta ut dessa och göra alla på samma gång och sedan vänta tills du fått svar och antingen processa när alla är klara eller om det går börja arbeta direkt med dessa.

Arch - Makepkg, not war -||- Asus Crosshair Hero VI -||- GSkill 16GiB DDR4 15-15-15-35-1T 3600Mhz -||- AMD 1600x @ 4.1GHz -||- nVidia MSI 970 Gaming -||- Samsung 850 Pro -||- EVEGA G2 750W -||- Corsair 570x -||- Asus Xonar Essence STX -||- Sennheiser HD-650 -||
Arch Linux, one hell of a distribution.

Trädvy Permalänk
Medlem
Registrerad
Jul 2017

En for-loop kommer inte vänta på att ett svar från en HTTP-request körs klart innan den kör vidare vilket jag tolkade hans fråga som.

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Sep 2008

Här är en enkel json fetch där du låter två separata promises köras och returnerar den som blir klar först (antingen får du json från servern eller så körs en timeouts om det tog för lång tid).

fetchJSON(server, timeout) { let timer = new Promise((resolve, reject) => { setTimeout(reject, timeout, 'request timed out'); }); let f = new Promise((resolve, reject) => { fetch(server, { method: 'GET', dataType: 'json', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }}) .then(response => response.json()) .then(json => resolve(json)) .catch(reject) }); return Promise .race([timer, f]) .then(json => this._successConnection(json)) .catch(err => this._errorConnection(err)); } _successConnection(json) { return json; } _errorConnection(err) { return false; }

Trädvy Permalänk
Medlem
Registrerad
Jun 2017
Skrivet av Commander:

Dock så har du promises som kan användas redan idag. Finns miljontals exempel på nätet både med och utan Jquery.

Är medveten eftersom det var Promise jag nämnde och kom med exempel på i posten innan. Svarade bara på att Async som den andra skribenten nämnde (som antagligen kommer ersätta Promise i många fall) kräver Babel just nu.

Trädvy Permalänk
Medlem
Registrerad
Aug 2017
Skrivet av Alling:

Sen undrar jag om du inte kan göra uppdateringen mindre imperativt och mer deklarativt?

const KEY = 'theData'; function updateData(data) { return data; // identity update } const oldData = JSON.parse(localStorage.getItem(KEY)); const newData = oldData.map(updateData); localStorage.setItem(KEY, JSON.stringify(newData));

Det här känns som den bästa lösningen.