Hvorfor Bruke redusere() for å Sekvensielt Løse Løfter Fungerer

0
19

Skrive asynkron JavaScript uten å bruke Løftet objektet er mye som å bake en kake med øynene lukket. Det kan bli gjort, men det skal bli rotete, og du vil trolig ende opp med å brenne deg.

Jeg vil ikke si det er nødvendig, men du får ideen. Det er virkelig fin. Noen ganger, skjønt, er det behov for litt hjelp til å løse noen av unike utfordringer, som når du prøver å sekvensielt løse en haug av lover i orden, den ene etter den andre. Et triks som dette er nyttig, for eksempel når du gjør noen form for batch prosessering via AJAX. Du vil at serveren skal behandle en haug av ting, men ikke alle på en gang, så du plass behandlingen ut over tid.

Dommen ut pakker som bidrar til å gjøre denne oppgaven enklere (som Caolan McMahon asynkron bibliotek), det oftest anbefalt forslag til løsning for sekvensielt å løse løfter er å bruke Tabellen.prototypen.redusere(). Som du kanskje allerede har hørt om dette. Ta en samling av ting, og redusere dem til en enkelt verdi, som dette:

la resultat = [1,2,5].redusere((akkumulator, element) => {
tilbake akkumulator + elementet.
}, 0); // <– Vår første verdi.

– konsollen.logg(resultat); // 8

Men, når du bruker redusere() for vårt formål, oppsett ser mer ut som dette:

la userIDs = [1,2,3];

userIDs.redusere( (previousPromise, nextID) => {
tilbake previousPromise.deretter(() => {
tilbake methodThatReturnsAPromise(nextID);
});
}, Løftet.løse());

Eller, i en mer moderne format:

la userIDs = [1,2,3];

userIDs.redusere( asynkron (previousPromise, nextID) => {
venter previousPromise;
tilbake methodThatReturnsAPromise(nextID);
}, Løftet.løse());

Dette er ryddig! Men for den lengste tiden, jeg bare svelget denne løsningen og kopiert som del av koden inn i mitt program fordi det “virket”. Dette innlegget er meg å ta en stikke på å forstå to ting:

  1. Hvorfor denne tilnærmingen fungerer?
  2. Hvorfor kan vi ikke bruke andre Utvalg metoder for å gjøre det samme?

Hvorfor dette fungerer?

Husk, det viktigste formålet med å redusere() er å “redusere” en haug av ting i en ting, og det gjør det ved å lagre opptil resultatet i akkumulatoren som loop går. Men som akkumulator trenger ikke å være numeriske. Loopen kan vende tilbake til hva det vil (som et løfte), og resirkulere som verdi gjennom innb. hver iterasjon. Spesielt, uansett hva akkumulator verdien er, loop i seg selv aldri endrer sin atferd — herunder dens tempo i gjennomføringen. Det holder bare å rulle gjennom samlingen så fort som tråden gjør.

Dette er stort å forstå, fordi det trolig går mot hva du tror skjer i løpet av denne løkken (i det minste, det gjorde det for meg). Når vi bruker det til å sekvensielt løse lover, redusere() loop er egentlig ikke bremse ned i det hele tatt. Det er helt synkron, gjør sitt normale ting så fort som det kan, akkurat som alltid.

Se på følgende kodebit og legg merke til hvordan fremdriften av loopen er ikke hindres på alt av løfter tilbake i innb..

funksjonen methodThatReturnsAPromise(nextID) {
tilbake nye Lover((løse, avvis) => {
setTimeout(() => {

– konsollen.logg(`Løse! ${dayjs().format(‘hh:mm:ss’)}`);

løse();
}, 1000);
});
}

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

– konsollen.logg(`Loop! ${dayjs().format(‘hh:mm:ss’)}`);

tilbake accumulatorPromise.deretter(() => {
tilbake methodThatReturnsAPromise(nextID);
});
}, Løftet.løse());

I våre konsollen:

“Loop! 11:28:06”
“Loop! 11:28:06”
“Loop! 11:28:06”
“Løse! 11:28:07”
“Løse! 11:28:08”
“Løse! 11:28:09”

De løfter løse i rekkefølge som vi forventer, men loop i seg selv er rask, jevn, og synkron. Etter å se på MDN polyfill for å redusere(), dette er fornuftig. Det er ingenting asynkron om en stund() loop utløsende innb.() over og over igjen, og det er hva som skjer under panseret:

while (k < len) {
if (k o) {
value = tilbakeringing(verdi, v[k], k, o);
}
k++;
}

Med alt dette i tankene, den virkelige magien oppstår i dette stykket her:

tilbake previousPromise.deretter(() => {
tilbake methodThatReturnsAPromise(nextID)
});

Hver gang våre innb. branner, vi kommer tilbake et løfte som løser til en annen lover. Og samtidig redusere() ikke vent for en hvilken som helst oppløsning for å ta plass, den nytte det gir muligheten til å passere noe tilbake i samme innb. etter hver kjøring, en funksjon som er unik for å redusere(). Som et resultat, kan vi bygge en kjede av lover som kan løse inn i flere lover, noe som gjør alt fint og sekvensiell:

nye Lover( (løse, avvis) => {
// Løfte #1

løse();
}).deretter( (resultat) => {
// Løfte #2

return resultat;
}).deretter( (resultat) => {
// Løfte #3

return resultat;
}); // … og så videre!

Alt dette bør også avsløre hvorfor kan vi ikke bare gå tilbake til en enkelt, nye lover hver iterasjon. Fordi løkken kjører synkront, hver løftet vil bli sparket umiddelbart, i stedet for å vente for de som er opprettet før det.

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

– konsollen.logg(`Loop! ${dayjs().format(‘hh:mm:ss’)}`);

tilbake nye Lover((løse, avvis) => {
setTimeout(() => {
– konsollen.logg(`Løse! ${dayjs().format(‘hh:mm:ss’)}`);
løse(nextID);
}, 1000);
});
}, Løftet.løse());

I våre konsollen:

“Loop! 11:31:20”
“Loop! 11:31:20”
“Loop! 11:31:20”
“Løse! 11:31:21”
“Løse! 11:31:21”
“Løse! 11:31:21”

Er det mulig å vente til alle behandlingen er ferdig før du gjør noe annet? Ja. Den synkrone natur redusere() betyr ikke at du ikke kan kaste en part etter hvert element har blitt ferdigbehandlet. Utseende:

funksjonen methodThatReturnsAPromise(id) {
tilbake nye Lover((løse, avvis) => {
setTimeout(() => {
– konsollen.logg(`Behandling ${id}`);
løse(id);
}, 1000);
});
}

la resultat = [1,2,3].redusere( (accumulatorPromise, nextID) => {
tilbake accumulatorPromise.deretter(() => {
tilbake methodThatReturnsAPromise(nextID);
});
}, Løftet.løse());

resultatet.deretter(e => {
– konsollen.logg(“Oppløsning er komplett! La oss part.”)
});

Siden alle er vi tilbake i vår tilbakering er lenket lover, det er alt vi får når løkken er ferdig: et løfte. Etter det vi kan håndtere det, men vi ønsker, selv lenge etter redusere() har kjørt sitt løp.

Hvorfor vil ikke noen andre Utvalg metodene fungerer?

Husk, under panseret på å redusere(), kan vi ikke vente for våre innb. å fullføre før du går til neste punkt. Det er helt synkron. Det samme gjelder for alle disse andre metoder:

  • Tabellen.prototypen.kart()
  • Tabellen.prototypen.forEach()
  • Tabellen.prototypen.filteret()
  • Tabellen.prototypen.noen()
  • Tabellen.prototypen.hver()

Men redusere() er spesiell.

Vi fant at grunnen redusere() arbeider for oss, er fordi vi er i stand til å returnere noe rett tilbake til den samme tilbakeringing (nemlig et løfte), som vi kan bygge på ved å ha det løser inn i en annen lover. Med alle disse andre metodene, men vi bare ikke kan passere et argument til vår innb. som ble returnert fra vår tilbakeringing. I stedet, hver av dem innb. argumenter er forutbestemt, noe som gjør det umulig for oss å utnytte dem for noe som sekvensiell løfte oppløsning.

[1,2,3].kart((element, [indeks, array]) => [verdi]);
[1,2,3].filteret((element, [indeks, array]) => [boolsk]);
[1,2,3].noen((element, [indeks, array]) => [boolsk]);
[1,2,3].hver((element, [indeks, array]) => [boolsk]);

Jeg håper dette hjelper!

I det minste, jeg håper dette bidrar til å kaste lys på hvorfor redusere() er unikt kvalifisert til å håndtere løfter på denne måten, og kanskje gi deg en bedre forståelse av hvordan vanlige Array metoder operere under panseret. Har jeg gått glipp av noe? Få noe galt? Gi meg beskjed!