DanLevy.net

Smetti di forzare async/await

Le promesse sono davvero di tendenza.

Hero image for Smetti di forzare async/await

Dal principio dei tempi, gli sviluppatori hanno combattuto molte lotte inutili. Dal classico “Tabs vs. Spaces” al senza tempo dibattito “Mac vs. PC”, siamo bravi a trovare argomenti di distrazione.


Risposte: Linux & Spaces.

La lotta…?

Promises vs. Async/Await!

Aspetta, è una lotta? Deve esserlo, vero? Non ne parliamo più di callback?

No,non è una lotta. In definitiva è semplicemente un altro possibile strumento nel tuo arsenale. Tuttavia, poiché async/await non sostituisce tutte le funzionalità di Promise (in particolare Promise.all, .race) presentarlo come un sostituto è fuorviante.

Molte persone influenti promuovono questo equivoco: async/await è il sostituto delle Promise che tutti hanno aspettato per.

Suggerimento: No, assolutamente no, e neanche per poco.

Una recente aggiunta a VS Code alimenta questo bias. Come ha twittato @umaar:

Visual Studio Code can now convert your long chains of Promise.then()‘s into async/await! 🎊 Works very well in both JavaScript and TypeScript files. .catch() is also correctly converted to try/catch ✅ pic.x.com/xb39Lsp84V

— Umar Hansa (@umaar) September 28, 2018

Se odi le Promise e vuoi questa funzionalità di refactoring, non ti biasimo.


Empatizzo. Capisco.


Ci sono passato. 🤗


Usavo odiare le Promise. Oggi sono tornato indietro completamente. Le Promise sono fantastiche. Ti consentono/incentivano a sfruttare la composizione di funzioni.

Ci sono 2 aree su cui consiglio di concentrarsi per prima cosa per migliorare la tua tecnica con le Promise.

  1. Funzioni con nome (niente anonime)
  2. Funzioni a scopo unico

#1: Funzioni con nome!

Elimina i metodi anonimi. L’uso di funzioni con nome fa sì che il codice legga come poesia dei tuoi requisiti.

Diamo un’occhiata a un esempio comune:

Eseguire una richiesta HTTP GET usando fetch:

Anti‑Pattern

// ❌ Using anonymous inline functions 💩
fetch(url)
.then(response => response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus)))
.then(response => response.text())

Soluzione: Metodi con nome

// ✅ Clarity emerges: named functions
fetch(url)
.then(checkResponse)
.then(getText)
// Reusable general-purpose functions
function checkResponse(response) {
return response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus))
}
function getText(response) {
return response.text()
}

I vantaggi di questo approccio diventano sempre più evidenti man mano che il codice diventa più DRY.

Risorse aggiuntive: Dai un’occhiata ai miei video da 1 minuto su log di base e debug avanzato usando questa tecnica.

#2: Scopo Unico (Funzioni)

Suona sorprendentemente preciso: Scopo Unico.

Eppure è così soggettivo, arbitrario e, a volte, persino privo di senso.

// 1 punto: il return & ternario sono effettivamente una riga unica
function checkResponse(response) {
return response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus))
}
// 1 punto: il return & l'espressione sono anch'essi una riga unica
function getText(response) {
return response.text()
}

Dato il codice di una funzione, aggiungi 1 punto per ogni riga che contiene uno dei seguenti token: if, return, ternario, for, const, let, var, switch, while, [].map/filter/reduce/etc. Aggiungi 1 punto per ogni istruzione (ignora le righe vuote). Una catena di espressioni o metodi conta solo come 1 punto.

Uff, un po’ di gergo.

Interessante, la maggior parte degli sviluppatori afferma di essere abbastanza bravi a mantenere il Singolo Scopo del loro codice. Non è un caso isolato: dicono anche di essere ottimi guidatori!

Diamo un’occhiata a un esempio presentato dal (incredibilmente talentuoso) Jake Archibald nel suo articolo su async/await per il sito Google Developers (nota: 2024, link rimosso).

// source: https://developers.google.com/web/fundamentals/primers/async-functions
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});
// log them in order
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}

Scopo unico?

Direi di no. Cosa fa logInOrder?

  1. itera su una lista di urls
  2. le passa a una chiamata HTTP GET inline:
    1. fetch HTTP
    2. restituisce il corpo della risposta come testo
  3. aggiunge un .then(text => console.log(text)) dopo ogni promessa in textPromise
    1. stampa i risultati in serie

Ci sono 5 metodi anonimi definiti in questa singola funzione. Come osserva Jake, il .reduce è troppo complesso. Non ha senso scrivere a mano meccanismi sfumati in tutto il codice. In altre parole, non scriviamo codice di creazione DOM con infinite chiamate a document.createElement(), element.setAttribute(), ecc. Invece scegliamo lo strumento migliore tra le varie opzioni: funzioni di supporto/utility, librerie o framework.

Soluzione: Funzioni a Scopo Unico

Inizia estrapolando i metodi

VS Code refactor extracting async methods from Promise code

Prosegui sostituendo il .reduce() e logPromise() con un Promise.all e un ..map()

Refactored Promise chain using Promise all and map for readability

Prova ad applicare queste tecniche al tuo codice! Poi mandami un tweet e fammi sapere come è andata. Oppure, se hai domande o commenti, contattami pure!

Aiuta a diffondere #PromiseTruth e condividi questo articolo. ❤️

credit: matt-nelson-414464-unsplash.webp

Letture correlate