JavaScript è un linguaggio di programmazione a thread singolo, il che significa che è possibile eseguire solo un’attività alla volta. Poiché esiste un solo thread di esecuzione, sorge una domanda: in che modo gli sviluppatori eseguono un’attività di lunga durata senza influire sul thread di esecuzione? Bene, la risposta è semplice: programmazione asincrona.
La programmazione asincrona in JavaScript offre un ottimo set di funzionalità per la gestione delle operazioni di input/output (IO) che vengono eseguite immediatamente e non hanno risposta immediata. La maggior parte delle applicazioni oggigiorno richiede in qualche modo una programmazione asincrona. Fare richieste AJAX (Asynchronous JavaScript and XML) attraverso la rete è un buon caso d’uso della programmazione asincrona in JavaScript.
Nella prossima sezione discuteremo di come la programmazione asincrona influisce sul processo di sviluppo.
Elaborazione thread singolo in JavaScript
JavaScript viene eseguito su un singolo thread, il che significa che quando viene eseguito nel browser, tutto il resto si interrompe; questo è necessario perché le modifiche al DOM non possono essere eseguite su thread paralleli. Considera una situazione in cui un thread sta aggiungendo elementi figlio al nodo e l’altro sta reindirizzando un URL: non è uno scenario ideale.
JavaScript gestisce l’elaborazione delle attività molto rapidamente in piccoli blocchi, quindi la maggior parte degli utenti raramente se ne accorge. Ad esempio, l’ascolto di un evento di clic del pulsante, l’esecuzione del calcolo e l’aggiornamento del DOM: tutto ciò viene eseguito dietro le quinte, nascosto all’utente. Una volta completata tale operazione, il browser è libero di scegliere una nuova attività dalla coda. L’utente, in questo caso, non è affatto consapevole dell’esistenza della coda.
Programmazione asincrona JavaScript e callback
L’elaborazione a thread singolo non è adatta a molte situazioni. Considera uno scenario in cui JavaScript incontra un processo lento come l’esecuzione di operazioni sui dati su un server o l’esecuzione di richieste AJAX. Queste operazioni potrebbero richiedere diversi secondi o addirittura minuti; il browser verrà bloccato mentre attende la risposta. Nel caso di NodeJS, l’applicazione non sarà in grado di elaborare ulteriori richieste.
In JavaScript, la soluzione è la programmazione asincrona. Invece di attendere il completamento di un’operazione, un processo chiamerà un altro processo (come una funzione) quando il risultato è pronto. Questo è un processo noto come Richiama; viene passato a qualsiasi funzione asincrona come argomento. Quello che segue è un esempio di utilizzo Richiamate nel codice JavaScript asincrono:
doCalculationAsync(callback1); console.log('Process finished'); // call when doCalculationAsync completes function callback1(error) { if (!error) console.log('Calculation completed'); }
Qui, doCalculationAsync() accetta una funzione di callback come parametro; richiamata1 verrà eseguito ad un certo punto nel tempo in futuro, indipendentemente da quanto tempo doCalculationAsync() prenderà. Di seguito è riportato l’output di questo codice una volta eseguito:
Process finished Calculation completed
Cos’è Callback Hell?
Considera il seguente codice:
asyncFunction1((err, res) => { if (!err) asyncFunction2(res, (err, res) => { if (!err) asyncFunction3(res, (err, res) => { console.log('async1, async2, async3 completed'); }); }); });
Come puoi vedere, il codice sopra è difficile da capire e gestire; può peggiorare ulteriormente quando viene aggiunta la logica di gestione degli errori. Questo è ciò che è noto come inferno di richiamata.
Sul lato client, il callback hell è relativamente raro e può arrivare fino a tre livelli di profondità se si effettuano chiamate AJAX o si aggiorna DOM. Fortunatamente, tuttavia, l’inferno del callback è gestibile.
La situazione si complica un po’ con le applicazioni lato server. Ad esempio, un’applicazione NodeJS potrebbe avere a che fare con la ricezione di upload di file, la scrittura di log o l’esecuzione di ulteriori chiamate API. L’inferno di callback può davvero essere problematico in tali situazioni.
Javascript moderno propone un approccio per risolvere questo problema chiamato promesse.
Prevenire l’inferno da richiamata con le promesse
Le promesse sono state introdotte in ES6. Le promesse vengono trattate come funzioni wrapper attorno alle funzioni di callback. Ci consentono di gestire le operazioni asincrone in modo più gestibile. Fungono da segnaposto per i valori che non sono noti al momento della creazione. Gli utenti possono allegare un gestore per un’operazione riuscita o un motivo per l’errore. In questo modo, i metodi asincroni restituiscono valori come se fossero sincroni.
Per utilizzare Promises, utilizziamo il costruttore Promises. Riceve due parametri: risolvere e rifiutare – entrambi sono callback. Possiamo eseguire qualsiasi operazione asincrona all’interno dei callback, quindi risolverli se hanno esito positivo o rifiutarli in caso di errore.
Le promesse possono essere in uno dei tre possibili stati:
- In attesa di: La promessa non è ancora operativa
- Soddisfatto: La promessa si risolve dopo il completamento dell’operazione
- Respinto: Promise non riesce a completare la sua operazione
Ecco un esempio che mostra le promesse JavaScript nel codice:
const promise = new Promise((resolve, reject) => { if (success) { resolve('async action successful!') } else { reject(throw new Error('Something happened. Couldn’t complete async action')) } })
Questa funzione restituisce una nuova promessa, che verrebbe avviata con a in sospeso stato. Qui, risolvere e rifiutare sono callback. Quando una promessa viene risolta con successo, allora si dice che sia nel soddisfatto stato, mentre, d’altra parte, se la promessa non riesce a eseguire un’operazione o restituisce un errore allora si dice che si trova in un respinto stato.
Facciamo uso della promessa che abbiamo creato sopra:
promise.then((data) => { console.log(data) // async action successful! }).catch((error) => { console.log(error) // Something happened. Couldn’t complete async action }).finally(() => { console.log('Run when the promise must have settled') })
Notare la finalmente() blocco qui viene utilizzato solo per scopi di pulizia del codice. Gestisce altre cose quando la promessa ha completato le sue operazioni.
Leggi: Programmazione asincrona in ECMAScript.
Concatenamento delle promesse in JavaScript
Uno dei vantaggi dell’utilizzo delle promesse è il fatto che possono essere concatenate. È possibile concatenarne un paio insieme per eseguire ulteriori attività asincrone, una dopo l’altra. Di seguito è riportato un esempio che illustra come concatenare le promesse in JavaScript:
asyncOperation('http://localhost:8080') .then(asyncGetSessionDetails) .then(asyncGetUserDetails) .then(asyncLoginAccess) .then(result => { console.log('action completed'); return result; }) .catch(error => { console.log('error occured', error); });
Problemi con le promesse
Le promesse risolvono il problema del callback hell ma introducono una propria serie di problemi, che includono:
- La sintassi sembra più complicata dei callback.
- Poiché la sintassi è un po’ complicata, il debug può essere più difficile.
- L’intera catena di promesse è asincrona. Qualsiasi funzione che utilizza una serie di promesse dovrebbe restituire la stessa promessa.
Asincrono/Attendi in JavaScript
Introdotto in ECMAScript nel 2017, Asincrono/Attendi consente di scrivere promesse in modo più semplice e visivamente accattivante. Agisce come zucchero sintattico sopra le promesse. È stato un grande miglioramento utilizzato per gestire le operazioni asincrone.
NotaNota: le funzioni asincrone utilizzano le promesse nascoste, quindi è importante prima capire come funzionano le promesse.
Insieme a Asincrono/Attendi è possibile scrivere codice asincrono basato su promesse proprio come se fosse codice sincrono senza bloccare il thread principale. Uno degli altri vantaggi di Asincrono/Attendi è che possiamo liberarci di poi() Catene.
Ecco un esempio di come usare Asincrono/Attendi in JavaScript:
async function verifyUserInfo() { try { const connection = await asyncDBconnect('http://localhost:1234'), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return log; } catch (error) { console.log('error', error); return null; } } // self-executing async function (async () => { await verifyUserInfo(); })();
In questo esempio, la funzione verificaUserInfo() è preceduto da asincrono parola chiave.
I metodi possono essere resi asincroni scrivendo asincrono prima del loro nome; quando tali metodi vengono chiamati, restituiscono una promessa. Non appena la promessa viene restituita, dovrebbe essere risolta o rifiutata.
Il aspetta la parola chiave qui è garantire che l’elaborazione venga completata prima dell’esecuzione del comando successivo.
Vantaggi di Async/Await in JavaScript
Asincrono/Attendi fornisce una sintassi migliore e più chiara e semplifica la gestione delle operazioni asincrone. Come promessa viene fornita con un sacco di boilerplate, Asincrono/Attendi mettici sopra uno zucchero sintattico. Diamo un’occhiata ai vantaggi di Asincrono/Attendi in JavaScript:
- Sintassi più pulita con meno parentesi, semplificando il debug
- La gestione degli errori è migliore; prova a prendere può essere utilizzato nello stesso modo in cui viene utilizzato nel codice sincrono.
- Più facile da codificare
Leggi: Testare i metodi asincroni utilizzando run() e waitFor().
JavaScript asincrono
La programmazione asincrona è sempre stata una sfida nel mondo della programmazione. In questo blog abbiamo parlato dell’evoluzione della programmazione asincrona in JavaScript, dai callback alle promesse di Asincrono/Attendi.
JavaScript consente solo una cosa alla volta perché viene eseguito su un singolo thread. Per rendere i processi più veloci, è stato introdotto AJAX, che ha aiutato a far evolvere l’aggiornamento del processo di sviluppo web. Tenendo conto di ciò, abbiamo discusso alcuni principi primari e avanzati di JavaScript in modo da poter gestire in modo intelligente AJAX e JavaScript asincrono nel 2021.