JavaScript-återuppringningar, löften och Async-funktioner Del 1

Introduktion

Det finns mycket prat om asynkron programmering, men vad är exakt den stora affären? Storheten är att vi vill att vår kod ska vara non-blocking. 

Uppgifter som kan blockera vår ansökan inkluderar att göra HTTP-förfrågningar, fråga en databas eller öppna en fil. Vissa språk, som Java, hanterar detta genom att skapa flera trådar. Men JavaScript har bara en tråd, så vi behöver utforma våra program så att ingen uppgift blockerar flödet. 

Asynkron programmering löser detta problem. Det låter oss genomföra uppgifter senare så att vi inte håller upp hela programmet och väntar på att uppgifter ska slutföras. Det hjälper också när vi vill se till att uppgifterna utförs i följd. 

I en del av denna handledning lär vi oss begreppen bakom synkron och asynkron kod och ser hur vi kan använda återuppringningsfunktioner för att lösa problem med asynkroni.

Innehåll

  • Trådar
  • Synkron vs Asynkron
  • Återuppringningsfunktioner
  • Sammanfattning
  • Medel

Trådar

Jag vill att du ska komma ihåg den sista gången du handlade på mataffären. Det fanns förmodligen flera kassaregister öppna för att kolla kunder. Detta hjälper butiksprocessen mer transaktioner på samma tid. Detta är ett exempel på samtidighet. 

För att uttrycka det enkelt gör samtidighet mer än en uppgift samtidigt. Ditt operativsystem är samtidigt eftersom det kör flera processer samtidigt. En process är en exekveringsmiljö eller en instans av en löpande applikation. Till exempel är din webbläsare, textredigerare och antivirusprogram alla processer på din dator som körs samtidigt.

Ansökningar kan också vara samtidiga. Detta uppnås med trådar. Jag kommer inte att bli för djup eftersom det här går utöver omfattningen av denna artikel. Om du vill ha en fördjupad förklaring av hur JavaScript fungerar under huven rekommenderar jag att du tittar på den här videon. 

En tråd är en enhet inom en process som kör kod. I vårt butiksexempel är varje kassalinje en tråd. Om vi ​​bara har en kassalinje i affären, skulle det förändra hur vi behandlade kunder. 

Har du någonsin varit i linje och haft något att hålla upp din transaktion? Kanske behövde du en priskontroll, eller var tvungen att se en chef. När jag är på postkontoret försöker skicka ett paket och jag inte har mina etiketter fyllda, frågar kassan mig att gå åt sidan medan de fortsätter att kolla andra kunder. När jag är klar går jag tillbaka till framsidan av linjen för att checkas ut. 

Detta liknar hur asynkron programmering fungerar. Kassören kunde ha väntat på mig. Ibland gör de det. Men det är en bättre upplevelse att inte hålla upp linjen och kolla in de andra kunderna. Poängen är att kunder inte behöver checkas ut i den ordning de är i linje. På samma sätt behöver koden inte utföras i den ordning i vilken vi skriver den. 

Synkron vs Asynkron

Det är naturligt att tänka på vår kod som genomförs sekventiellt från topp till botten. Detta är synkron. Men med JavaScript är vissa uppgifter naturligtvis asynkrona (t.ex. setTimeout) och vissa uppgifter vi designar är asynkrona eftersom vi vet att de kan blockera. 

Låt oss ta en titt på ett praktiskt exempel med filer i Node.js. Om du vill prova kodexemplen och behöver en primer på att använda Node.js kan du hitta instruktioner från den här handledningen. I det här exemplet öppnar vi en fil med inlägg och hämtar en av inläggen. Då öppnar vi en fil med kommentarer och hämtar kommentarer till det inlägget. 

Detta är det synkrona sättet:

index.js

const fs = kräver ('fs'); const path = kräver ('path'); const postsUrl = path.join (__ dirname, 'db / posts.json'); const commentsUrl = path.join (__ dirname, 'db / comments.json'); // returnera data från vår filfunktion loadCollection (url) försök const response = fs.readFileSync (url, 'utf8'); returnera JSON.parse (svar);  fångst (fel) console.log (error);  // returnera ett objekt med id-funktionen getRecord (samling, id) return collection.find (funktion (element) return element.id == id;);  // returnera en rad kommentarer för en postfunktion getCommentsByPost (kommentarer, postId) return comments.filter (funktion (kommentar) return comment.postId == postId;);  // initialiseringskod const posts = loadCollection (postsUrl); const post = getRecord (inlägg, "001"); const kommentarer = loadCollection (commentsUrl); const postComments = getCommentsByPost (kommentarer, post.id); console.log (post); console.log (postComments);

db / posts.json

["id": "001", "titel": "Hälsning", "text": "Hej världen", "Författare": "Jane Doe", "id": "002", "Titel" "JavaScript 101", "Text": "Grundläggande för programmering.", "Författare": "Alberta Williams", "id": "003", "Titel": "Async Programming" Återuppringningar, Löften och Async / Await. "," Författare ":" Alberta Williams "] 

db / comments.json

["id": "phx732", "postId": "003", "text": "Jag får inte det här återuppringningsämnet." , "id": "avj9438", "postId": "003", "text": "Detta är verkligen användbar information." , "id": "gnk368", "postId": "001", "text": "Detta är en test kommentar." ]

De readFileSync Metoden öppnar filen synkront. Därför kan vi också skriva vår initialiseringskod på ett synkront sätt. Men det här är inte det bästa sättet att öppna filen eftersom det här är en potentiellt blockerande uppgift. Öppna filen ska utföras asynkront så att flödet av körning kan vara kontinuerligt. 

Node har a readfile metod vi kan använda för att öppna filen asynkront. Detta är syntaxen:

fs.readFile (url, 'utf8', funktion (fel, data) ...); 

Vi kan bli frestad att returnera våra data inom denna återuppringningsfunktion, men det kommer inte att vara tillgängligt för oss att använda inuti vårt loadCollection fungera. Vår initialiseringskod behöver också ändras eftersom vi inte har rätt värden att tilldela våra variabler. 

För att illustrera problemet, låt oss titta på ett enklare exempel. Vad tror du att följande kod kommer att skrivas ut?

funktionuppgift1 () setTimeout (funktion () console.log ('first');, 0);  funktionen task2 () console.log ('second');  funktionsuppgift3 () console.log ("third");  uppgift 1(); task2 (); task3 ();

Detta exempel kommer att skriva ut "andra", "tredje" och sedan "första". Det spelar ingen roll att setTimeout funktionen har en 0-fördröjning. Det är en asynkron uppgift i JavaScript, så det kommer alltid att skjutas upp för att köras senare. De firstTask funktionen kan representera en asynkron uppgift, som att öppna en fil eller fråga vår databas. 

En lösning för att våra uppgifter ska kunna utföras i den ordning vi vill ha är att använda återuppringningsfunktioner.

Återuppringningsfunktioner

För att använda återuppringningsfunktioner skickar du en funktion som en parameter till en annan funktion, och sedan ringer funktionen när din uppgift är klar. Om du behöver en primer om hur du använder högre orderfunktioner, har reaktivex en interaktiv handledning du kan prova. 

Återuppringningar låter oss tvinga uppgifter att genomföras sekventiellt. De hjälper oss också när vi har uppgifter som beror på resultatet av en tidigare uppgift. Med hjälp av återuppringningar kan vi fixa vårt senaste exempel så det kommer att skriva ut "första", "andra" och sedan "tredje".

funktion först (cb) setTimeout (funktion () return cb ('first');, 0);  funktion andra (cb) return cb ('second');  funktion tredje (cb) return cb ('third');  först (funktion (result1) console.log (result1); andra (funktion (result2) console.log (result2); tredje (funktion (result3) console.log (result3););); );

I stället för att skriva ut strängen inuti varje funktion returnerar vi värdet inuti en återuppringning. När vår kod körs skriver vi ut det värde som skickades till vår återuppringning. Detta är vad vår readfile funktionsanvändning. 

Återgå till våra filer exempel kan vi ändra vår loadCollection funktion så att den använder återuppringningar för att läsa filen på ett asynkront sätt.

funktion loadCollection (url, återuppringning) fs.readFile (url, 'utf8', funktion (fel, data) om (fel) console.log (error); else return callback (JSON.parse ;); 

Och det här är vad vår initialiseringskod kommer att se ut som att använda callbacks:

loadCollection (postsUrl, funktion (inlägg) loadCollection (commentsUrl, funktion (kommentarer) getRecord (inlägg, "001", funktion (post) const postComments = getCommentsByPost (kommentarer, post.id); console.log (post); console.log (postkommentarer););););

En sak att märka i vår loadCollection funktionen är att istället för att använda a försök fånga uttalande att hantera fel använder vi en om annat påstående. Fångstblocket skulle inte kunna fånga fel som returnerats från readfile ring tillbaka. 

Det är bra att ha felhanterare i vår kod för fel som är resultatet av yttre påverkan i motsats till programmeringsfel. Detta inkluderar att komma åt filer, ansluta till en databas eller göra en HTTP-begäran. 

I det reviderade kodexemplet inkluderade jag inte någon felhantering. Om ett fel skulle inträffa vid något av stegen skulle programmet inte fortsätta. Det skulle vara trevligt att ge meningsfulla instruktioner.

Ett exempel på när felhantering är viktigt är om vi har en uppgift att logga in på en användare. Det innebär att du får ett användarnamn och lösenord från ett formulär, frågar vår databas för att se om det är en giltig kombination och omdirigera sedan användaren till deras instrumentpanel om vi lyckas. Om användarnamnet och lösenordet var ogiltigt upphörde appen att springa om vi inte berätta vad som ska göras. 

En bättre användarupplevelse skulle vara att returnera ett felmeddelande till användaren och låta dem försöka försöka logga in. I vårt filexempel kunde vi vidarebefordra ett felobjekt tillsammans i återuppringningsfunktionen. Detta är fallet med readfile fungera. Då, när vi kör koden, kan vi lägga till en om annat uttalande för att hantera det framgångsrika resultatet och det avvisade resultatet.

Uppgift

Använd återuppringningsmetoden genom att skriva ett program som öppnar en fil med användare, välj en användare och öppna en fil med inlägg och skriva ut användarens information och alla inlägg.

Sammanfattning

Asynkron programmering är en metod som används i vår kod för att skjuta upp händelser för senare körning. När du har att göra med en asynkron uppgift är återuppringningar en lösning för att tima våra uppgifter så att de utför sekventiellt. 

Om vi ​​har flera uppgifter som beror på resultatet av tidigare uppgifter, är en lösning att använda flera inbädda återuppringningar. Detta kan dock leda till ett problem som kallas "callback hell". Löften löser problemet med callback hell, och asyncfunktionerna låter oss skriva vår kod på ett synkront sätt. I del 2 av denna handledning lär vi oss vad de är och hur de ska användas i vår kod.

Medel

  • Philip Roberts: Vad Heck är Event Loop Hur som helst?
  • Samtidighet i Java
  • Du vet inte JavaScript: Async och Performance
  • Node.js Event Loop
  • Blockering kontra icke-blockering