JavaScript Callbacks, Promises e Async Functions Parte 1

introduzione

Si parla molto della programmazione asincrona, ma quale è esattamente il grosso problema? Il grosso problema è che vogliamo che il nostro codice non blocchi. 

Le attività che possono bloccare la nostra applicazione includono l'esecuzione di richieste HTTP, l'interrogazione di un database o l'apertura di un file. Alcuni linguaggi, come Java, affrontano questo problema creando più thread. Tuttavia, JavaScript ha solo un thread, quindi abbiamo bisogno di progettare i nostri programmi in modo che nessuna attività stia bloccando il flusso. 

La programmazione asincrona risolve questo problema. Ci consente di eseguire attività in un secondo momento, in modo da non ostacolare l'intero programma in attesa del completamento delle attività. Aiuta anche quando vogliamo garantire che le attività vengano eseguite in sequenza. 

Nella prima parte di questo tutorial, impareremo i concetti alla base del codice sincrono e asincrono e vediamo come possiamo usare le funzioni di callback per risolvere i problemi con l'asincronia.

Contenuto

  • discussioni
  • Sincrono contro asincrono
  • Funzioni di callback
  • Sommario
  • risorse

discussioni

Voglio che tu ricordi l'ultima volta che hai fatto shopping al supermercato. Probabilmente c'erano molti registratori di cassa aperti per controllare i clienti. Ciò consente al negozio di elaborare più transazioni nello stesso lasso di tempo. Questo è un esempio di concorrenza. 

Per dirla semplicemente, la concorrenza sta facendo più di un compito allo stesso tempo. Il tuo sistema operativo è concorrente perché esegue più processi contemporaneamente. Un processo è un ambiente di esecuzione o un'istanza di un'applicazione in esecuzione. Ad esempio, il browser, l'editor di testo e il software antivirus sono tutti processi sul computer che vengono eseguiti contemporaneamente.

Le applicazioni possono anche essere concomitanti. Questo è ottenuto con i thread. Non entrerò troppo in profondità perché questo va oltre lo scopo di questo articolo. Se desideri una spiegazione approfondita di come JavaScript funziona sotto il cofano, ti consiglio di guardare questo video. 

Un thread è un'unità all'interno di un processo che sta eseguendo il codice. Nel nostro esempio di negozio, ogni riga di pagamento sarebbe una discussione. Se abbiamo una sola linea di pagamento nel negozio, ciò cambierebbe il modo in cui abbiamo elaborato i clienti. 

Sei mai stato in linea e hai avuto qualcosa in attesa della tua transazione? Forse avevi bisogno di un controllo dei prezzi, o dovevi vedere un manager. Quando sono all'ufficio postale cercando di spedire un pacco e non ho le etichette piene, il cassiere mi chiede di fare un passo indietro mentre continuano a controllare gli altri clienti. Quando sono pronto, torno in prima linea per essere controllato. 

Questo è simile a come funziona la programmazione asincrona. Il cassiere avrebbe potuto aspettarmi. A volte lo fanno. Ma è un'esperienza migliore non tenere la fila e controllare gli altri clienti. Il punto è che i clienti non devono essere controllati nell'ordine in cui sono in linea. Allo stesso modo, il codice non deve essere eseguito nell'ordine in cui lo scriviamo. 

Sincrono contro asincrono

È naturale pensare che il nostro codice venga eseguito sequenzialmente dall'alto verso il basso. Questo è sincrono. Tuttavia, con JavaScript, alcune attività sono intrinsecamente asincrone (ad es. SetTimeout) e alcune attività progettate per essere asincrone perché sappiamo in anticipo che possono bloccare. 

Diamo un'occhiata ad un esempio pratico usando i file in Node.js. Se vuoi provare gli esempi di codice e hai bisogno di un primer sull'uso di Node.js, puoi trovare le istruzioni per iniziare da questo tutorial. In questo esempio, apriremo un file di post e recupereremo uno dei post. Quindi apriremo un file di commenti e recupereremo i commenti per quel post. 

Questo è il modo sincrono:

index.js

const fs = require ('fs'); const path = require ('path'); const postsUrl = path.join (__ dirname, 'db / posts.json'); const commentsUrl = path.join (__ dirname, 'db / comments.json'); // restituisce i dati dalla nostra funzione file loadCollection (url) try const response = fs.readFileSync (url, 'utf8'); restituisce JSON.parse (risposta);  catch (errore) console.log (errore);  // restituisce un oggetto dalla funzione id getRecord (collection, id) return collection.find (function (element) return element.id == id;);  // restituisce una matrice di commenti per una funzione post getCommentsByPost (commenti, postId) return comments.filter (function (comment) return comment.postId == postId;);  // codice di inizializzazione const posts = loadCollection (postsUrl); const post = getRecord (posts, "001"); const comments = loadCollection (commentsUrl); const postComments = getCommentsByPost (commenti, post.id); console.log (post); console.log (postComments);

db / posts.json

["id": "001", "titolo": "Saluto", "testo": "Ciao mondo", "autore": "Jane Doe", "id": "002", "titolo": "JavaScript 101", "testo": "I fondamenti della programmazione.", "Autore": "Alberta Williams", "id": "003", "titolo": "Programmazione asincrona", "testo": " Callbacks, Promises and Async / Await. "," Author ":" Alberta Williams "] 

db / comments.json

["id": "phx732", "postId": "003", "testo": "Non ho questa roba di richiamata." , "id": "avj9438", "postId": "003", "testo": "Questa informazione è davvero utile." , "id": "gnk368", "postId": "001", "testo": "Questo è un commento di prova." ]

Il readFileSync metodo apre il file in modo sincrono. Pertanto, possiamo scrivere il nostro codice di inizializzazione anche in modo sincrono. Ma questo non è il modo migliore per aprire il file perché si tratta di un'attività potenzialmente bloccante. L'apertura del file deve essere eseguita in modo asincrono in modo che il flusso di esecuzione possa essere continuo. 

Il nodo ha un readFile metodo che possiamo usare per aprire il file in modo asincrono. Questa è la sintassi:

fs.readFile (url, 'utf8', function (error, data) ...); 

Potremmo essere tentati di restituire i nostri dati all'interno di questa funzione di callback, ma non sarà disponibile per l'uso all'interno del nostro loadCollection funzione. Il nostro codice di inizializzazione dovrà cambiare anche perché non avremo i valori corretti da assegnare alle nostre variabili. 

Per illustrare il problema, diamo un'occhiata ad un esempio più semplice. Cosa pensi che verrà stampato il seguente codice??

function task1 () setTimeout (function () console.log ('first');, 0);  function task2 () console.log ('second');  function task3 () console.log ('terzo');  task1 (); TASK2 (); TASK3 ();

Questo esempio stamperà "second", "third" e quindi "first". Non importa che il setTimeout la funzione ha un ritardo 0. È un'attività asincrona in JavaScript, quindi verrà sempre posticipata per l'esecuzione successiva. Il firstTask la funzione può rappresentare qualsiasi attività asincrona, come aprire un file o interrogare il nostro database. 

Una soluzione per ottenere i nostri compiti da eseguire nell'ordine che vogliamo è usare le funzioni di callback.

Funzioni di callback

Per utilizzare le funzioni di callback, si passa una funzione come parametro a un'altra funzione e quindi si chiama la funzione al termine dell'attività. Se hai bisogno di un primer su come utilizzare le funzioni di ordine superiore, reactivex ha un tutorial interattivo che puoi provare. 

Le callback ci consentono di forzare le attività da eseguire in sequenza. Ci aiutano anche quando abbiamo compiti che dipendono dai risultati di un'attività precedente. Usando i callback, possiamo correggere il nostro ultimo esempio in modo che stampi "first", "second" e quindi "third".

function first (cb) setTimeout (function () return cb ('first');, 0);  function second (cb) return cb ('second');  function third (cb) return cb ('third');  first (function (result1) console.log (result1); second (function (result2) console.log (result2); third (function (result3) console.log (result3););); );

Invece di stampare la stringa all'interno di ogni funzione, restituiamo il valore all'interno di un callback. Quando il nostro codice viene eseguito, stampiamo il valore che è stato passato al nostro callback. Questo è ciò che è nostro readFile funzione utilizza. 

Tornando al nostro esempio di file, possiamo cambiare il nostro loadCollection funzione in modo che utilizzi i callback per leggere il file in modo asincrono.

function loadCollection (url, callback) fs.readFile (url, 'utf8', function (error, data) if (errore) console.log (errore); else return callback (JSON.parse (data)) ;); 

E questo è come apparirà il nostro codice di inizializzazione usando i callback:

loadCollection (postsUrl, function (posts) loadCollection (commentsUrl, function (commenti) getRecord (post, "001", function (post) const postComments = getCommentsByPost (commenti, post.id); console.log (post); console.log (postComments););););

Una cosa da notare nel nostro loadCollection la funzione è quella invece di usare a prova a prendere dichiarazione per gestire gli errori, usiamo un se altro dichiarazione. Il blocco catch non sarebbe in grado di rilevare gli errori restituiti dal file readFile richiama. 

È buona norma avere nel nostro codice gestori di errori per errori dovuti a influenze esterne anziché a bug di programmazione. Ciò include l'accesso ai file, la connessione a un database o l'esecuzione di una richiesta HTTP. 

Nell'esempio di codice revisionato, non ho incluso alcuna gestione degli errori. Se si verifica un errore in uno qualsiasi dei passaggi, il programma non continuerà. Sarebbe bello fornire istruzioni significative.

Un esempio di quando la gestione degli errori è importante è se abbiamo un compito per accedere a un utente. Ciò implica ottenere un nome utente e una password da un modulo, interrogare il nostro database per vedere se si tratta di una combinazione valida e quindi reindirizzare l'utente al loro cruscotto se abbiamo successo. Se il nome utente e la password non fossero validi, la nostra app smetterebbe di funzionare se non dicessimo cosa fare. 

Un'esperienza utente migliore sarebbe quella di restituire un messaggio di errore all'utente e consentire loro di riprovare ad accedere. Nel nostro esempio di file, potremmo passare un oggetto errore lungo la funzione di callback. Questo è il caso con il readFile funzione. Quindi, quando eseguiamo il codice, possiamo aggiungere un se altro dichiarazione per gestire l'esito positivo e l'esito respinto.

Compito

Utilizzando il metodo di callback, scrivi un programma che aprirà un file di utenti, seleziona un utente, quindi apri un file di post e stampa le informazioni dell'utente e tutti i loro post.

Sommario

La programmazione asincrona è un metodo utilizzato nel nostro codice per rinviare gli eventi per l'esecuzione successiva. Quando si ha a che fare con un'attività asincrona, i callback sono una soluzione per sincronizzare le attività in modo che vengano eseguite in sequenza. 

Se disponiamo di più attività che dipendono dal risultato di attività precedenti, una soluzione consiste nell'utilizzare più callback nidificati. Tuttavia, questo potrebbe portare a un problema noto come "inferno di callback". Le promesse risolvono il problema con l'inferno del callback e le funzioni asincrone consentono di scrivere il nostro codice in modo sincrono. Nella parte 2 di questo tutorial, impareremo cosa sono e come usarli nel nostro codice.

risorse

  • Philip Roberts: What the Heck è l'Event Loop Anyway?
  • Concorrenza in Java
  • Non sai JavaScript: Async e prestazioni
  • Node.js Event Loop
  • Blocco contro non bloccante