Promesse rotte?
Errori che cadono, risultati persi...
Le Promise di JavaScript sono rotte?
Nei tempi antichi
Uno dei miti più comuni sulle Promise riguarda i presunti problemi con la gestione degli errori.
Molti anni fa le Promise erano davvero disastrose con gli errori. È stato fatto un grande lavoro per risolvere il problema.
E così, è stato risolto, anche ampiamente implementato.
La gente ha festeggiato
E purtroppo, alcuni non se ne sono accorti.
Ai giorni nostri
Il mito persiste ancora, lo vedo ovunque: articoli popolari su medium, su dzone, e molti altri fonti.
Ammetto che anche le risorse e la documentazione “ufficiali” offrono spesso esempi deboli e cattive abitudini. Questi vengono spesso usati per “dimostrare” il caso contro le Promise. Alcuni suggeriscono persino “rimedi” che peggiorano di molto le cose. (nota: link rimosso)
Regole per restare nei guai
- Le Promise hanno bisogno di qualcosa a cui aggrapparsi
- Sempre
returndalle tue funzioni.
- Sempre
- Usa istanze
Errorreali- Sempre usa istanze
Error.
- Sempre usa istanze
- Gestisci gli errori dove ha senso
- Sempre usa
.catch(), almeno una volta.
- Sempre usa
- Aggiungi chiarezza con funzioni nominate 🦄✨
- Preferisci funzioni nominate.
#1 Le Promise hanno bisogno di qualcosa a cui aggrapparsi
È fondamentale che tu faccia sempre return dalle tue funzioni.
Le funzioni callback delle Promise seguono un certo schema in .then(callback) e .catch(callback).
Ogni valore restituito viene passato alla callback del .then() successivo.
function addTen(number) { return number + 10;}
Promise.resolve(10) // 10 .then(addTen) // 20 .then(addTen) // 30 .then(addTen) // 40 .then(console.log) // logs "40"Bonus del “sempre restituire”: il codice è molto più facile da testare con unit test.
Domanda: Quanti stati distinti di Promise (risolti e rifiutati) sono stati creati?
Domanda: Quante promise sono state create nell’esempio precedente?
#2 Usa istanze Error reali
JavaScript ha un comportamento interessante intorno agli errori (che si applica al codice asincrono e sincrono.)
[vedi esempio su repl.it:throwing errors in javascript]
Per ottenere dettagli utili sul numero di riga e sullo stack di chiamate, devi usare istanze Error. Lanciare stringhe non funziona come in Python o Ruby.
Mentre JavaScript sembra gestire throw "stringa", come vedrai la stringa nel tuo handler catch. Tuttavia, i dati sono tutto ciò che vedrai*. Nessun stack frame precedente sarà incluso.
Esempi corretti con new Error:
throw new Error('message') // ✅Promise.reject(new Error('message')) // ✅throw Error('message') // ✅Promise.reject(Error('message')) // ✅I seguenti sono anti-pattern comuni:
throw 'error message' // ❌Promise.reject(-42) // ❌#3 Gestisci gli errori dove ha senso
Le Promise offrono un modo elegante per gestire gli errori, usando .catch(). È fondamentalmente un tipo speciale di .then() - dove vengono gestiti gli errori dai .then() precedenti. Vediamo un esempio…
Promise.resolve(42) .then(() => 'hello') .catch(() => console.log('will not get hit')) .then(() => throw new Error('totes fail')) .catch(() => console.log('WILL get hit'))Mentre .catch() può sembrare un handler di eventi DOM (es. click, keypress). Il suo posizionamento è importante, poiché può solo “catturare” gli errori lanciati sopra di esso.
Sovrascrivere gli errori è relativamente banale Restituisci un valore non-errore nella tua callback .catch(), la catena di Promise passa a eseguire le callback .then() in sequenza. (Effettivamente.)
Prova a seguire la sequenza del seguente esempio:
Promise.resolve(42) .then(() => 'hello') .then(() => throw new Error('totes fail')) .catch(() => { return 99 }) .then(num => num + 1) .then(console.log) // expected output: 100La sequenza è ciò che è importante da comprendere.
Anche se è un esempio sciocco, è progettato per illustrare come errori e dati fluiscono nelle Promise.
Ecco un riepilogo della sequenza:
- 42 è il valore iniziale.
helloviene sempre restituito dal metodo successivo.- ignoriamo il valore precedente e lanciamo un errore con il messaggio
'totes fail'. .catch()intercetta l’errore, invece restituisce99che sarà gestito da qualsiasi.then()successivo.- incrementa il
num, restituendo100 - il metodo
console.logriceve100e lo stampa! :tada:
Domanda: Cosa succede quando ci sono 2 .catch() in sequenza? Il secondo può mai essere eseguito? Riesci a pensare a un caso d’uso?
Domanda: Come può .catch() ignorare gli errori? Come potresti impedire che gli errori forzino un’uscita anticipata da Promise.all?
#4 Aggiungi chiarezza con funzioni nominate 🦄✨
Confronta la leggibilità dei seguenti 2 esempi:
Anonimo: ❌
Promise.resolve(10) // 10 .then(x => x * 2) // 20 .then(x => x / 4) // 5 .then(x => x * x) // 25 .then(x => x.toFixed(2)) // "25.00" .then(x => console.log(x)) // expected output: "25.00"Nominato: ✅
Promise.resolve(10) // 10 .then(double) // 20 .then(quarter) // 5 .then(square) // 25 .then(format) // "25.00" .then(log) // expected output: "25.00"
const double = x => x * 2const quarter = x => x / 4const square = x => x * xconst format = x => x.toFixed(2)const log = x => console.log(x)BONUS: ✅
Compatibile con i metodi degli Array!!!
Puoi riutilizzare le tue funzioni nominate con i nostri amici di Array.prototype. Inclusi .map(), .filter(), .every(), .some(), .find()!
Pipeline di collection #FTW:
// È LA STESSA COSA :mindblown:
[10, 20] // [ 10, 20 ] .map(double) // [ 20, 40 ] .map(quarter) // [ 5, 10 ] .map(square) // [ 25, 100 ] .map(format) // [ "25.00", "100.00" ] .map(log) // expected 2 lines of output: "25.00", "100.00"E se non vuoi fare questo tipo di coding lineare… Beh, hai funzioni semplici!
Puoi usarle come ti serve:
// Pattern di annidamento// ❌ per favore non farlo, comunque
const result = format(square(quarter(double(10))))
log(result)// expected output: "25.00"Perché l’annidamento di funzioni è un anti-pattern?
- Non è leggibile per tante persone
- I diff di git non rivelano facilmente chi ha cambiato cosa
- Difficile da debuggare o loggare dal mezzo delle funzioni annidate