Node.js
Programmazione asincrona
Ricerca…
introduzione
Il nodo è un linguaggio di programmazione in cui tutto può essere eseguito in modo asincrono. Di seguito è possibile trovare alcuni esempi e le cose tipiche del funzionamento asincrono.
Sintassi
- doSomething ([args], function ([argsCB]) {/ * fa qualcosa quando è fatto * /});
- doSomething ([args], ([argsCB]) => {/ * fa qualcosa quando viene fatto * /});
Funzioni di callback
Funzioni di callback in JavaScript
Le funzioni di callback sono comuni in JavaScript. Le funzioni di callback sono possibili in JavaScript perché le funzioni sono cittadini di prima classe .
Callback sincroni.
Le funzioni di callback possono essere sincrone o asincrone. Poiché le funzioni di callback asincrone possono essere più complesse, ecco un semplice esempio di una funzione di callback sincrono.
// a function that uses a callback named `cb` as a parameter
function getSyncMessage(cb) {
cb("Hello World!");
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getSyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
L'output per il codice sopra riportato è:
> Before getSyncMessage call
> Hello World!
> After getSyncMessage call
Innanzitutto analizzeremo come viene eseguito il codice precedente. Questo è più per coloro che non comprendono già il concetto di callback se già lo capisci, sentiti libero di saltare questo paragrafo. Prima viene analizzato il codice e quindi la prima cosa interessante che si verifica è che viene eseguita la riga 6 che emette Before getSyncMessage call
alla console. Quindi viene eseguita la riga 8 che chiama la funzione getSyncMessage
invia in una funzione anonima come argomento per il parametro cb
nella funzione getSyncMessage
. L'esecuzione è ora eseguita all'interno della funzione getSyncMessage
sulla riga 3 che esegue la funzione cb
che è stata appena getSyncMessage
, questa chiamata invia una stringa di argomento "Hello World" per il parametro denominato message
nella funzione anonima passata. L'esecuzione quindi va alla riga 9 che registra Hello World!
alla console. Quindi l'esecuzione passa attraverso il processo di uscita dal callstack ( vedere anche ) che colpisce la linea 10 quindi la linea 4 e infine torna alla riga 11.
Alcune informazioni da sapere sui callback in generale:
- La funzione che invii a una funzione come richiamata può essere chiamata zero volte, una volta o più volte. Tutto dipende dall'implementazione.
- La funzione di callback può essere chiamata in modo sincrono o asincrono e possibilmente in modo sincrono e asincrono.
- Proprio come le normali funzioni, i nomi che dai parametri alla tua funzione non sono importanti ma l'ordine è. Così, per esempio, sulla riga 8 il
message
parametri potrebbe essere stato chiamatostatement
,msg
, o se non hai senso qualcosa di simile ajellybean
. Quindi dovresti sapere quali parametri sono stati inviati alla tua callback in modo da poterli ottenere nell'ordine giusto con nomi propri.
Callback asincroni.
Una cosa da notare su JavaScript è che è sincrono di default, ma ci sono delle API date nell'ambiente (browser, Node.js, ecc.) Che potrebbero renderlo asincrono (c'è di più a riguardo qui ).
Alcune cose comuni che sono asincrone negli ambienti JavaScript che accettano i callback:
- eventi
- setTimeout
- setInterval
- l'API di recupero
- promesse
Anche qualsiasi funzione che utilizza una delle funzioni sopra può essere avvolta con una funzione che richiede una richiamata e il callback sarebbe quindi una richiamata asincrona (sebbene il wrapping di una promessa con una funzione che richiede una richiamata sia probabilmente considerato un anti-pattern come ci sono più modi preferiti per gestire le promesse).
Quindi data questa informazione possiamo costruire una funzione asincrona simile alla precedente sincrona.
// a function that uses a callback named `cb` as a parameter
function getAsyncMessage(cb) {
setTimeout(function () { cb("Hello World!") }, 1000);
}
console.log("Before getSyncMessage call");
// calling a function and sending in a callback function as an argument.
getAsyncMessage(function(message) {
console.log(message);
});
console.log("After getSyncMessage call");
Che stampa quanto segue alla console:
> Before getSyncMessage call
> After getSyncMessage call
// pauses for 1000 ms with no output
> Hello World!
L'esecuzione della linea passa ai registri della linea 6 "Prima della chiamata getSyncMessage". Quindi l'esecuzione passa alla riga 8 chiamando getAsyncMessage con un callback per il parametro cb
. Viene quindi eseguita la riga 3 che chiama setTimeout con un callback come primo argomento e il numero 300 come secondo argomento. setTimeout
fa tutto ciò che fa e trattiene quel callback in modo che possa chiamarlo più tardi in 1000 millisecondi, ma dopo aver impostato il timeout e prima che metta in pausa i 1000 millisecondi restituisce l'esecuzione al punto in cui era stata interrotta, quindi va alla riga 4 , quindi la riga 11, quindi si interrompe per 1 secondo e setTimeout chiama la sua funzione di richiamata che riprende l'esecuzione alla riga 3 dove getAsyncMessages
richiamato il callback getAsyncMessages
con il valore "Hello World" per il message
parametro che viene quindi registrato sulla console alla riga 9 .
Funzioni di callback in Node.js
NodeJS ha callback asincroni e in genere fornisce due parametri alle tue funzioni a volte convenzionalmente chiamati err
e data
. Un esempio con la lettura di un file di testo.
const fs = require("fs");
fs.readFile("./test.txt", "utf8", function(err, data) {
if(err) {
// handle the error
} else {
// process the file text given with data
}
});
Questo è un esempio di callback che viene chiamato una volta sola.
È buona pratica gestire l'errore in qualche modo anche se lo si registra o lo si gira. Il resto non è necessario se si lancia o si torna e può essere rimosso per ridurre il rientro finché si interrompe l'esecuzione della funzione corrente nel se facendo qualcosa come il lancio o il ritorno.
Sebbene possa essere comune vedere err
, i data
potrebbero non essere sempre il caso in cui i callback useranno tale modello, è meglio guardare la documentazione.
Un altro esempio di callback proviene dalla libreria express (espressa 4.x):
// this code snippet was on http://expressjs.com/en/4x/api.html
const express = require('express');
const app = express();
// this app.get method takes a url route to watch for and a callback
// to call whenever that route is requested by a user.
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
Questo esempio mostra una richiamata che viene chiamata più volte. Il callback è fornito con due oggetti come parametri qui denominati come req
e res
questi nomi corrispondono rispettivamente a richiesta e risposta, e forniscono modi per visualizzare la richiesta in entrata e impostare la risposta che verrà inviata all'utente.
Come puoi vedere ci sono vari modi in cui un callback può essere usato per eseguire il codice sincrono e asincrono in JavaScript e le callback sono molto diffuse in tutto il codice JavaScript.
Esempio di codice
Domanda: Qual è l'output del codice qui sotto e perché?
setTimeout(function() {
console.log("A");
}, 1000);
setTimeout(function() {
console.log("B");
}, 0);
getDataFromDatabase(function(err, data) {
console.log("C");
setTimeout(function() {
console.log("D");
}, 1000);
});
console.log("E");
Uscita: questo è noto con certezza: EBAD
. C
è sconosciuto quando verrà registrato.
Spiegazione: Il compilatore non si fermerà sui getDataFromDatabase
setTimeout
e getDataFromDatabase
. Quindi la prima riga che registrerà è E
Le funzioni di callback (primo argomento di setTimeout
) verranno eseguite dopo il timeout impostato su un modo asincrono!
Più dettagli:
-
E
non hasetTimeout
-
B
ha un timeout impostato di 0 millisecondi -
A
ha un timeout impostato di 1000 millisecondi -
D
deve richiedere un database, dopo cheD
deve attendere 1000 millisecondi quindi viene dopoA
-
C
è sconosciuto perché è sconosciuto quando vengono richiesti i dati del database. Potrebbe essere prima o dopoA
Gestione degli errori asincroni
Prova a prendere
Gli errori devono sempre essere gestiti. Se si utilizza la programmazione sincrona, è possibile utilizzare un try catch
. Ma questo non funziona se lavori asincroni! Esempio:
try {
setTimeout(function() {
throw new Error("I'm an uncaught error and will stop the server!");
}, 100);
}
catch (ex) {
console.error("This error will not be work in an asynchronous situation: " + ex);
}
Gli errori asincroni verranno gestiti solo all'interno della funzione di callback!
Possibilità di lavoro
Gestori di eventi
Le prime versioni di Node.JS hanno un gestore di eventi.
process.on("UncaughtException", function(err, data) {
if (err) {
// error handling
}
});
domini
All'interno di un dominio, gli errori vengono rilasciati tramite gli emettitori di eventi. Usando questo sono tutti gli errori, i timer, i metodi di callback implicitamente registrati solo all'interno del dominio. In caso di errore, inviare un evento di errore e non arrestare l'applicazione.
var domain = require("domain");
var d1 = domain.create();
var d2 = domain.create();
d1.run(function() {
d2.add(setTimeout(function() {
throw new Error("error on the timer of domain 2");
}, 0));
});
d1.on("error", function(err) {
console.log("error at domain 1: " + err);
});
d2.on("error", function(err) {
console.log("error at domain 2: " + err);
});
Callback hell
L'inferno del callback (anche una piramide di doom o effetto boomerang) si verifica quando si annidano troppe funzioni di callback all'interno di una funzione di callback. Ecco un esempio per leggere un file (in ES6).
const fs = require('fs');
let filename = `${__dirname}/myfile.txt`;
fs.exists(filename, exists => {
if (exists) {
fs.stat(filename, (err, stats) => {
if (err) {
throw err;
}
if (stats.isFile()) {
fs.readFile(filename, null, (err, data) => {
if (err) {
throw err;
}
console.log(data);
});
}
else {
throw new Error("This location contains not a file");
}
});
}
else {
throw new Error("404: file not found");
}
});
Come evitare "Callback Hell"
Si consiglia di annidare non più di 2 funzioni di callback. Questo ti aiuterà a mantenere la leggibilità del codice e sarà molto più facile mantenerlo in futuro. Se hai bisogno di nidificare più di 2 callback, prova a utilizzare gli eventi distribuiti .
Esiste anche una libreria chiamata async che aiuta a gestire i callback e la loro esecuzione disponibile su npm. Aumenta la leggibilità del codice di callback e offre un maggiore controllo sul flusso del codice di callback, incluso il permesso di eseguirli in parallelo o in serie.
Promesse native
Le promesse sono uno strumento per la programmazione asincrona. In JavaScript promesse sono noti per le loro then
metodi. Le promesse prevedono due stati principali "in attesa" e "risolti". Una volta che la promessa è 'risolta', non può tornare 'in sospeso'. Ciò significa che le promesse sono valide soprattutto per eventi che si verificano solo una volta. Lo stato 'sistemato' ha due stati pure 'risolti' e 'rifiutati'. Puoi creare una nuova promessa usando la new
parola chiave e passando una funzione nel costruttore new Promise(function (resolve, reject) {})
.
La funzione passata nel costruttore Promise riceve sempre un primo e un secondo parametro, solitamente denominati rispettivamente resolve
e reject
. La denominazione di questi due parametri è convenzione, ma metteranno la promessa nello stato 'risolto' o nello stato 'rifiutato'. Quando uno di questi è chiamato la promessa passa dall'essere "in sospeso" a "risolto". resolve
viene chiamata quando l'azione desiderata, che è spesso asincrona, è stata eseguita e il reject
viene utilizzato se l'azione è stata errata.
Nel sotto timeout è una funzione che restituisce una Promessa.
function timeout (ms) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("It was resolved!");
}, ms)
});
}
timeout(1000).then(function (dataFromPromise) {
// logs "It was resolved!"
console.log(dataFromPromise);
})
console.log("waiting...");
uscita della console
waiting...
// << pauses for one second>>
It was resolved!
Quando viene chiamato timeout, la funzione passata al costruttore Promise viene eseguita senza ritardo. Quindi viene eseguito il metodo setTimeout e la richiamata viene attivata nei successivi ms
millisecondi, in questo caso ms=1000
. Poiché la richiamata al setTimeout non viene attivata, la funzione di timeout restituisce il controllo all'ambito della chiamata. La catena dei metodi then
viene quindi archiviata per essere chiamata più tardi quando / se la Promessa è stata risolta. Se esistessero metodi di catch
, anche questi sarebbero archiviati, ma verrebbero licenziati quando / se la promessa 'respinge'.
Lo script stampa quindi "in attesa ...". Un secondo dopo, setTimeout chiama il suo callback che chiama la funzione di risoluzione con la stringa "È stato risolto!". Tale stringa viene quindi passata al callback del metodo then
e quindi viene registrata all'utente.
Nello stesso senso è possibile racchiudere la funzione setTimeout asincrona che richiede una richiamata, quindi è possibile racchiudere qualsiasi azione asincrona singolare con una promessa.
Maggiori informazioni sulle promesse nella documentazione di JavaScript Promises .