Dan Levy

Smetti di forzare async/await

Le promesse sono davvero di tendenza.

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:

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