Varför Använder minska() jämfört med föregående kvartal Lösa Löften Verk

0
26

Att skriva med asynkron JavaScript-kod utan att använda Lovar objektet är en hel del som att baka en kaka med slutna ögon. Det kan göras, men det kommer att bli rörigt, och du kommer förmodligen att hamna bränna dig.

Jag säger inte att det är nödvändigt, men du får idén. Det är riktigt trevligt. Ibland, om, det behöver lite hjälp för att lösa några unika utmaningar, som när du försöker jämfört med föregående kvartal lösa en massa löften för det ena efter det andra. Ett trick som det är praktiskt, till exempel, när du gör någon form av batch-bearbetning via AJAX. Du vill att servern ska kunna bearbeta en massa saker, men inte alla på en gång, så att du utrymme behandling över tid.

Dom paket som hjälper till att göra denna uppgift lättare (som Caolan McMahon ‘ s async bibliotek), de vanligaste föreslagna lösning för sekventiellt lösa löften är att använda Array.prototyp.minska(). Du kanske har hört talas om detta. Ta en samling av saker, och att reducera dem till ett enda värde, som den här:

låt resultat = [1,2,5].minska((ackumulator, artikel) => {
tillbaka ackumulator + punkt.
}, 0); // <– Vår ursprungliga värdet.

konsolen.logga in(resultat); // 8

Men, när du använder minska() för våra syften, installationsprogrammet ser mer ut så här:

låt userIDs = [1,2,3];

userIDs.minska( (previousPromise, nextID) => {
tillbaka previousPromise.då(() => {
tillbaka methodThatReturnsAPromise(nextID);
});
}, Lovar.lösa());

Eller, i ett mer modernt format:

låt userIDs = [1,2,3];

userIDs.minska( asynkron (previousPromise, nextID) => {
väntar previousPromise;
tillbaka methodThatReturnsAPromise(nextID);
}, Lovar.lösa());

Det är snyggt! Men för den längsta tiden, jag bara svalde denna lösning och att kopieras bit av kod i min ansökan eftersom det “fungerade”. Det här inlägget är för mig att ta ett knivhugg på att förstå två saker:

  1. Varför denna metod även fungerar?
  2. Varför kan vi inte använda andra Rad metoder för att göra samma sak?

Varför är detta ens arbete?

Kom ihåg att det huvudsakliga syftet att minska() är att “minska” en massa saker i en sak, och det gör det genom att lagra upp resultat i ackumulatorn som loopen körs. Men det ackumulator behöver inte vara numeriska. Slingan kan återgå vad det vill (som ett löfte), och återvinna det värde genom återuppringning varje iteration. Framför allt, oavsett vad de ackumulator värde, loop själv aldrig ändrar sitt beteende — inklusive dess takten i genomförandet. Det bara rullar genom insamling så fort tråden gör.

Det här är stort för att förstå eftersom det förmodligen går det mot vad du tror det är som händer under denna loop (minst, det gjorde det för mig). När vi använder det för att sekventiellt lösa löften, minska() loop är faktiskt inte bromsar alls. Det är helt synkron, gör sitt normala så fort som möjligt, precis som alltid.

Titta på följande kodavsnitt och märker hur utvecklingen av slingan inte hindras av löftena tillbaka i returen.

funktion methodThatReturnsAPromise(nextID) {
återgå nya Löfte((lösa, avvisa) => {
setTimeout(() => {

konsolen.log(`Lösa! ${dayjs().format(‘hh:mm:ss’)}`);

lösa();
}, 1000);
});
}

[1,2,3].minska( (accumulatorPromise, nextID) => {

konsolen.logga in (“Loop! ${dayjs().format(‘hh:mm:ss’)}`);

tillbaka accumulatorPromise.då(() => {
tillbaka methodThatReturnsAPromise(nextID);
});
}, Lovar.lösa());

I vår konsol:

“Loop! 11:28:06”
“Loop! 11:28:06”
“Loop! 11:28:06”
“Lösa! 11:28:07”
“Lösa! 11:28:08”
“Lösa! 11:28:09”

De lovar att lösa så som vi förväntar oss, men slingan i sig är snabb, stadig och synkron. Efter att ha tittat på MDN polyfill för att minska(), gör denna mening. Det är inget asynkron om ett tag() loop utlöser återuppringning() om och om igen, vilket är vad som händer under huven:

medan (k < len) {
om k i o) {
värde = återuppringning(värde, o[k], k, o).
}
k++;
}

Med allt detta i åtanke, den verkliga magin sker i detta stycke här:

tillbaka previousPromise.då(() => {
tillbaka methodThatReturnsAPromise(nextID)
});

Varje gång våra återuppringning bränder, vi går tillbaka ett löfte som beslutar om ett annat löfte. Och samtidigt minska() inte väntar på någon resolution till att ta plats, fördelen det ger är förmågan att passera något tillbaka till samma återuppringning efter varje körning, en funktion som är unik för att minska(). Som ett resultat, kan vi bygga upp en kedja av löften om att lösa in mer löften, att göra allt fint och sekventiella:

nya Löfte( (lösa, avvisa) => {
// Lova #1

lösa();
}).då( (resultatet) => {
// Lova #2

return result;
}).då( (resultatet) => {
// Lova #3

return result;
}); // … och så vidare!

Allt detta bör också avslöja varför kan vi inte bara tillbaka en enda, nya löfte varje iteration. Eftersom slingan löper synkront, varje löfte kommer att få sparken omedelbart, i stället för att vänta på dem som skapade innan det.

[1,2,3].minska( (previousPromise, nextID) => {

konsolen.logga in (“Loop! ${dayjs().format(‘hh:mm:ss’)}`);

återgå nya Löfte((lösa, avvisa) => {
setTimeout(() => {
konsolen.log(`Lösa! ${dayjs().format(‘hh:mm:ss’)}`);
lösa(nextID);
}, 1000);
});
}, Lovar.lösa());

I vår konsol:

“Loop! 11:31:20”
“Loop! 11:31:20”
“Loop! 11:31:20”
“Lösa! 11:31:21”
“Lösa! 11:31:21”
“Lösa! 11:31:21”

Är det möjligt att vänta tills all bearbetning är klar innan du gör något annat? Ja. Synkron karaktär minska() betyder inte att du inte kan kasta en part efter varje punkt har varit helt behandlas. Utseende:

funktion methodThatReturnsAPromise(id) {
återgå nya Löfte((lösa, avvisa) => {
setTimeout(() => {
konsolen.logga in (Behandling ${id}`);
lösa(id);
}, 1000);
});
}

låt resultat = [1,2,3].minska( (accumulatorPromise, nextID) => {
tillbaka accumulatorPromise.då(() => {
tillbaka methodThatReturnsAPromise(nextID);
});
}, Lovar.lösa());

resultat.då e => {
konsolen.logga in(“Resolution är klar! Let ‘ s party.”)
});

Eftersom alla vi är på väg tillbaka i våra återuppringning är en fastkedjad lovar, det är allt vi får när slingan är klar: ett löfte. Efter att vi kan hantera det men vi vill ha, även långt efter att minska() har klingat av.

Varför inte någon annan Array metoder fungerar?

Kom ihåg, under huven på att minska(), vi väntar inte för vår återuppringning för att slutföra innan han går vidare till nästa punkt. Det är helt synkront. Det samma gäller för alla dessa andra metoder:

  • Array.prototyp.karta()
  • Array.prototyp.forEach()
  • Array.prototyp.filter()
  • Array.prototyp.vissa()
  • Array.prototyp.varje()

Men minska() är speciell.

Vi fann att anledningen till minska() fungerar för oss är att vi kan returnera något tillbaka till vår samma återuppringning (det vill säga, ett löfte), som vi sedan kan bygga vidare på genom att ha det att lösa in ett annat löfte. Med alla dessa andra metoder, men vi kan inte bara passera ett argument för att vår callback som var tillbaka från vår återuppringning. I stället var och en av dessa återuppringning argument är förutbestämt, vilket gör det omöjligt för oss att utnyttja dem för något som sekventiell lovar upplösning.

[1,2,3].karta((item, [index, array]) => [värde]);
[1,2,3].filter((item, [index, array]) => [boolean]);
[1,2,3].vissa((item, [index, array]) => [boolean]);
[1,2,3].varje((item, [index, array]) => [boolean]);

Jag hoppas att detta hjälper!

Åtminstone, jag hoppas att det här hjälper till att sprida lite ljus över varför minska() är unikt kvalificerade att hantera löften på detta sätt, och kanske ge dig en bättre förståelse av hur vanligt det är med Array-metoder fungerar under huven. Har jag missat något? Får något fel? Låt mig veta!