Så fungerar Node.js
I den här lektionen beskrivs hur Node.js hanterar inkommande uppgifter till JavaScript-körningen.
Typer av uppgifter
JavaScript-program har två typer av uppgifter:
- Synkrona uppgifter: Dessa uppgifter sker i ordning. De är inte beroende av en annan resurs som ska slutföras. Exempel är matematiska operationer eller strängmanipulering.
- Asynkron: Dessa uppgifter kanske inte slutförs omedelbart eftersom de är beroende av andra resurser. Exempel är nätverksbegäranden eller filsystemåtgärder.
Eftersom du vill att programmet ska köras så snabbt som möjligt vill du att JavaScript-motorn ska kunna fortsätta fungera medan den väntar på ett svar från en asynkron åtgärd. För att göra det läggs den asynkrona aktiviteten till i en aktivitetskö och fortsätter att arbeta med nästa uppgift.
Hantera aktivitetskö med händelseloop
Node.js använder JavaScript-motorns händelsedrivna arkitektur för att bearbeta asynkrona begäranden. Följande diagram illustrerar hur V8-händelseloopen fungerar på hög nivå:
En asynkron uppgift, som anges med lämplig syntax (visas nedan), läggs till i händelseloopen. Uppgiften innehåller det arbete som ska utföras och en återanropsfunktion för att ta emot resultaten. När den intensiva åtgärden är klar utlöses återanropsfunktionen med resultatet.
Synkrona åtgärder jämfört med asynkrona åtgärder
Node.js-API:erna tillhandahåller både asynkrona och synkrona åtgärder för vissa av samma åtgärder, till exempel filåtgärder. Även om du vanligtvis alltid bör tänka asynkront först, finns det tillfällen då du kan använda synkrona åtgärder.
Ett exempel är när ett kommandoradsgränssnitt (CLI) läser en fil och sedan omedelbart använder data i filen. I det här fallet kan du använda den synkrona versionen av filåtgärden eftersom det inte finns något annat system eller någon annan person som väntar på att använda programmet.
Men om du skapar en webbserver bör du alltid använda den asynkrona versionen av filåtgärden för att inte blockera körningsförmågan för en tråd att bearbeta andra användarbegäranden.
I ditt arbete som utvecklare på TailWind Traders måste du förstå skillnaden mellan synkrona och asynkrona åtgärder och när du ska använda var och en.
Prestanda via asynkrona åtgärder
Node.js drar nytta av den unika händelsedrivna karaktären hos JavaScript som gör det snabbt och högpresterande att skapa serveruppgifter. JavaScript, när det används korrekt med asynkrona tekniker, kan ge samma prestandaresultat som lågnivåspråk som C på grund av prestandaökningar som möjliggörs av V8-motorn.
De asynkrona teknikerna finns i tre formatmallar, som du måste kunna känna igen i ditt arbete:
- Async/await (rekommenderas): Den senaste asynkrona tekniken som använder nyckelorden
async
ochawait
för att ta emot resultatet av en asynkron åtgärd. Async/await används på många programmeringsspråk. I allmänhet använder nya projekt med nyare beroenden den här typen av asynkron kod. - Återanrop: Den ursprungliga asynkrona tekniken som använder en återanropsfunktion för att ta emot resultatet av en asynkron åtgärd. Du ser detta i äldre kodbaser och i äldre Node.js-API:er.
- Promises: En nyare asynkron teknik som använder ett löftesobjekt för att ta emot resultatet av en asynkron åtgärd. Du ser detta i nyare kodbaser och i nyare Node.js-API:er. Du kan behöva skriva löftesbaserad kod i ditt arbete för att omsluta äldre API:er som inte uppdateras. Genom att använda löften för den här omslutningen tillåter du att koden används i ett större antal node.js-versioner än i den nyare kodstilen async/await.
Async/await
Async/await är ett nytt sätt att hantera asynkron programmering. Async/await är syntaktisk socker utöver löften och får asynkron kod att se mer ut som synkron kod. Det är också lättare att läsa och underhålla.
Samma exempel med async/await ser ut så här:
// async/await asynchronous example
const fs = require('fs').promises;
const filePath = './file.txt';
// `async` before the parent function
async function readFileAsync() {
try {
// `await` before the async method
const data = await fs.readFile(filePath, 'utf-8');
console.log(data);
console.log('Done!');
} catch (error) {
console.log('An error occurred...: ', error);
}
}
readFileAsync()
.then(() => {
console.log('Success!');
})
.catch((error) => {
console.log('An error occurred...: ', error);
});
När async/await släpptes i ES2017 kunde nyckelorden bara användas i funktioner där den översta funktionen är ett löfte. Även om löftet inte behövde ha then
och catch
avsnitt, var det fortfarande nödvändigt att ha promise
syntax för att köra.
En async
funktion returnerar alltid ett löfte, även om den inte har något await
anrop inuti den. Löftet matchas med det värde som returneras av funktionen. Om funktionen utlöser ett fel avvisas löftet med värdet som genereras.
Löften
Eftersom kapslade återanrop kan vara svåra att läsa och hantera har Node.js lagt till stöd för löften. Ett löfte är ett objekt som representerar det slutliga slutförandet (eller felet) av en asynkron åtgärd.
En promise-funktion har formatet:
// Create a basic promise function
function promiseFunction() {
return new Promise((resolve, reject) => {
// do something
if (error) {
// indicate success
reject(error);
} else {
// indicate error
resolve(data);
}
});
}
// Call a basic promise function
promiseFunction()
.then((data) => {
// handle success
})
.catch((error) => {
// handle error
});
Metoden then
anropas när löftet uppfylls och catch
metoden anropas när löftet avvisas.
Om du vill läsa en fil asynkront med löften är koden:
// promises asynchronous example
const fs = require('fs').promises;
const filePath = './file.txt';
// request to read a file
fs.readFile(filePath, 'utf-8')
.then((data) => {
console.log(data);
console.log('Done!');
})
.catch((error) => {
console.log('An error occurred...: ', error);
});
console.log(`I'm the last line of the file!`);
Asynkron/väntar på toppnivå
De senaste versionerna av Node.js har lagt till async/await på översta nivån för ES6-moduler. Du måste lägga till en egenskap med namnet type
i package.json med värdet module
för för att använda den här funktionen.
{
"type": "module"
}
Sedan kan du använda nyckelordet await
på den översta nivån i koden
// top-level async/await asynchronous example
const fs = require('fs').promises;
const filePath = './file.txt';
// `async` before the parent function
try {
// `await` before the async method
const data = await fs.readFile(filePath, 'utf-8');
console.log(data);
console.log('Done!');
} catch (error) {
console.log('An error occurred...: ', error);
}
console.log("I'm the last line of the file!");
Återanrop
När Node.js ursprungligen släpptes hanterades asynkron programmering med hjälp av återanropsfunktioner. Återanrop är funktioner som skickas som argument till andra funktioner. När uppgiften är klar anropas återanropsfunktionen.
Ordningen på parametrarna för funktionen är viktig. Återanropsfunktionen är den sista parametern för funktionen.
// Callback function is the last parameter
function(param1, param2, paramN, callback)
Funktionsnamnet i koden som du underhåller kanske inte anropas callback
. Det kan anropas cb
eller done
.next
Namnet på funktionen är inte viktigt, men parametrarnas ordning är viktig.
Observera att det inte finns någon syntaktisk indikation på att funktionen är asynkron. Du måste veta att funktionen är asynkron genom att läsa dokumentationen eller fortsätta att läsa igenom koden.
Motringningsexempel med namngiven återanropsfunktion
Följande kod separerar funktionen async från återanropet. Det här är lätt att läsa och förstå och gör att du kan återanvända återanropet för andra asynkrona funktioner.
// callback asynchronous example
// file system module from Node.js
const fs = require('fs');
// relative path to file
const filePath = './file.txt';
// callback
const callback = (error, data) => {
if (error) {
console.log('An error occurred...: ', error);
} else {
console.log(data); // Hi, developers!
console.log('Done!');
}
};
// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function
fs.readFile(filePath, 'utf-8', callback);
console.log("I'm the last line of the file!");
Rätt resultat är:
I'm the last line of the file!
Hi, developers!
Done!
Först startas den asynkrona funktionen fs.readFile
och går in i händelseloopen. Sedan fortsätter kodkörningen till nästa kodrad, som är den sista console.log
. När filen har lästs anropas återanropsfunktionen och de två console.log-uttrycken körs.
Motringningsexempel med anonym funktion
I följande exempel används en anonym återanropsfunktion, vilket innebär att funktionen inte har något namn och inte kan återanvändas av andra anonyma funktioner.
// callback asynchronous example
// file system module from Node.js
const fs = require('fs');
// relative path to file
const filePath = './file.txt';
// async request to read a file
//
// parameter 1: filePath
// parameter 2: encoding of utf-8
// parmeter 3: callback function () => {}
fs.readFile(filePath, 'utf-8', (error, data) => {
if (error) {
console.log('An error occurred...: ', error);
} else {
console.log(data); // Hi, developers!
console.log('Done!');
}
});
console.log("I'm the last line of the file!");
Rätt resultat är:
I'm the last line of the file!
Hi, developers!
Done!
När koden körs startas den asynkrona funktionen fs.readFile
och hamnar i händelseloopen. Därefter fortsätter körningen till följande kodrad, som är den sista console.log
. När filen läss anropas återanropsfunktionen och de två console.log-uttrycken körs.
Kapslade återanrop
Eftersom du kan behöva anropa ett efterföljande asynkront återanrop och sedan ett annat kan återanropskoden bli kapslad. Detta kallas återanropshelvete och är svårt att läsa och underhålla.
// nested callback example
// file system module from Node.js
const fs = require('fs');
fs.readFile(param1, param2, (error, data) => {
if (!error) {
fs.writeFile(paramsWrite, (error, data) => {
if (!error) {
fs.readFile(paramsRead, (error, data) => {
if (!error) {
// do something
}
});
}
});
}
});
Synkrona API:er
Node.js har också en uppsättning synkrona API:er. Dessa API:er blockerar körningen av programmet tills uppgiften har slutförts. Synkrona API:er är användbara när du vill läsa en fil och sedan omedelbart använda data i filen.
Synkrona (blockerande) funktioner i Node.js använder namngivningskonventionen functionSync
för . Det asynkrona readFile
API:et har till exempel en synkron motsvarighet med namnet readFileSync
. Det är viktigt att upprätthålla den här standarden i dina egna projekt så att koden är lätt att läsa och förstå.
// synchronous example
const fs = require('fs');
const filePath = './file.txt';
try {
// request to read a file
const data = fs.readFileSync(filePath, 'utf-8');
console.log(data);
console.log('Done!');
} catch (error) {
console.log('An error occurred...: ', error);
}
Som ny utvecklare på TailWind Traders kan du bli ombedd att ändra valfri typ av Node.js-kod. Det är viktigt att förstå skillnaden mellan synkrona och asynkrona API:er och de olika syntaxerna för asynkron kod.