
TypeError: Cannot read properties of undefined (reading 'name') è la riga più comune nella
console di un browser, ed è anche una delle più fraintese. Quasi chiunque la veda si mette a
cercare la cosa chiamata name. La cosa chiamata name sta benissimo. L’errore riguarda qualunque
cosa avrebbe dovuto contenerla.
Questa è tutta la difficoltà di questo messaggio in una frase sola. Indica la proprietà che si stava cercando di leggere, e non dice nulla sulla variabile che si è rivelata vuota. Per questo la correzione non si trova mai dove punta il messaggio, e il primo minuto di ogni indagine si spende a capire di cosa parli davvero il messaggio.
Come leggere il messaggio
Si prenda il codice user.profile.name. Se user esiste ma non ha profile, allora
user.profile è undefined, e leggere .name da undefined genera un’eccezione. Il messaggio
dice (reading 'name'). Non dice che manca profile, e name è l’unica parola dell’espressione
che non è il problema.
Browser diversi formulano il messaggio in modo diverso, il che conta quando lo stesso bug arriva da tre persone diverse:
- Chrome ed Edge:
Cannot read properties of undefined (reading 'name'). Le versioni più vecchie dicevanoCannot read property 'name' of undefined, con lo stesso significato. - Firefox:
TypeError: user.profile is undefined, oppurecan't access property "name", user.profile is undefined. Firefox nomina l’espressione che era vuota, il che è più utile. - Safari:
TypeError: undefined is not an object (evaluating 'user.profile.name'). Safari fornisce l’intera espressione e lascia a chi legge il compito di capire quale parte sia fallita.
Tre formulazioni diverse, un solo bug. Un team che apre segnalazioni incollando la riga della console aprirà tre ticket distinti, e quello di Firefox è l’unico che indica dove guardare.
Esiste poi un errore simile, Cannot read properties of null, che non è esattamente lo stesso.
null è ciò che si ottiene da una ricerca che è stata eseguita e non ha trovato nulla:
document.querySelector('.total') su una pagina senza alcun elemento .total. undefined è ciò
che si ottiene da qualcosa che non è mai stato impostato: una proprietà che non esiste
sull’oggetto, una variabile dichiarata e mai assegnata, una funzione che non ha restituito nulla.
null di solito indica un problema nella pagina; undefined di solito indica un problema nei
dati.
Da dove viene l’undefined
Il messaggio è identico in ogni caso. La causa è una di poche possibili, e il nome della proprietà tra parentesi è un indizio discreto su quale sia.
I dati non sono ancora arrivati. La pagina si è renderizzata prima che tornasse la richiesta
che la doveva riempire, e il codice ha letto response.items.length mentre response era ancora
il segnaposto. È l’errore che compare sulle connessioni lente e non su quelle veloci, su un
telefono e non su un laptop, per il cliente e mai per lo sviluppatore. (reading 'length') e
(reading 'map') sono gli indizi tipici: qualcosa si aspettava un array e ha ricevuto nulla.
La struttura è cambiata. L’API restituiva sempre user.profile; ora restituisce
user.profile solo per gli account che lo hanno compilato, oppure ha rinominato la chiave, oppure
il campo si è spostato di un livello più in alto. Sul client non è cambiato nulla, e l’errore è
comparso il giorno del deploy di qualcun altro. È la versione che arriva sotto forma di “funzionava
ieri”.
La chiave è scritta in modo diverso. item.userId nel codice, item.user_id nella risposta.
Niente avvisa: JavaScript legge una proprietà che non esiste come undefined e prosegue, e
l’errore emerge solo un passo più tardi, quando qualcosa prova a leggere attraverso di essa.
È una lista vuota. results[0].title quando results è []. Codice perfettamente valido,
finché non arriva la prima ricerca che non trova corrispondenze. I dati di test sono raramente
vuoti; i dati di produzione spesso lo sono.
Qualcosa non ha restituito nulla. Una funzione con un percorso di codice che dimentica il
return, una funzione async il cui chiamante ha dimenticato di fare await e ha ricevuto una
promise invece di un valore, un .find() che non ha trovato corrispondenze. Tutti questi casi
producono undefined e lo passano alla riga successiva.
Il codice è minificato. In produzione il messaggio diventa Cannot read properties of
undefined (reading 'a'), perché la proprietà è stata rinominata dalla build. Il numero di riga
punta dentro un’unica riga enorme. Senza una source map il messaggio ha smesso di dire qualunque
cosa, e l’unica cosa che resta è la sequenza di richieste e clic che ha portato fin lì.
Come appare dall’altra parte
Niente di tutto questo è visibile a chi lo incontra. Quello che vede è una sezione della pagina che non si è mai riempita, un pulsante che non fa nulla, un modulo che non si invia. Nella pagina non compare mai la scritta “si è verificato un errore”, perché l’errore è avvenuto dentro uno script e lo script si è semplicemente fermato. La riga della console esiste, in un pannello che quella persona non ha mai aperto.
Così la segnalazione dice “la pagina degli ordini è vuota”. E lo sviluppatore la riproduce, con una connessione veloce, un account completo e una lista non vuota, e non è vuota.
- Debole
- La pagina degli ordini è vuota per un cliente, non è possibile riprodurre il problema.
- Migliore
- Console: TypeError: Cannot read properties of undefined (reading 'items'), orders.js line 214. La richiesta a /api/orders appena precedente ha restituito 200 con il corpo vuoto. Il cliente non ha ancora ordini.
La seconda versione è una correzione già pronta ad accadere. Tutto ciò che contiene viene dalla pagina nel momento del guasto: la riga della console, la richiesta precedente e la risposta che avrebbe dovuto contenere i dati. È questa l’evidenza di cui questo errore ha bisogno, ed è esattamente l’evidenza che una persona davanti a una pagina vuota non può fornire da sola. La nostra guida alla scrittura di una segnalazione di bug spiega cosa chiedere; in breve, per questo errore la console e il log di rete sono la segnalazione, e la descrizione è solo una didascalia.
Session Replay
Estensione Chrome gratuita. Un clic sulla pagina che si sta comportando male cattura lo screenshot, la console e il log di rete, e fornisce un link da incollare nel ticket.
Trovare la causa negli strumenti per sviluppatori
Quando si ha la pagina davanti e l’errore si riproduce, tre mosse risolvono la questione.
Sospendere sulle eccezioni. Nel pannello Sources di Chrome, l’icona di pausa con “Pause on
uncaught exceptions” ferma lo script sulla riga che ha generato l’eccezione, con ogni variabile
ancora nel proprio scope. Passando il mouse sull’espressione si vede quale parte di
user.profile.name è undefined, cosa che il messaggio non avrebbe mai potuto dire.
Guardare la richiesta precedente. Passare alla scheda Network e trovare la risposta che
avrebbe dovuto riempire l’oggetto. Nove volte su dieci la risposta è lì: un corpo vuoto, un 200
con un messaggio di errore al suo interno, una chiave con un nome diverso, oppure una richiesta che
non è mai partita perché era in attesa di qualcos’altro. L’articolo su
failed to fetch copre
il caso in cui la richiesta stessa è fallita; questo articolo riguarda il caso in cui è andata a
buon fine ma ha restituito una struttura sbagliata.
Controllare l’ordine degli eventi. Se l’errore compare solo a volte, quasi sempre è la prima causa vista sopra: una lettura che viene eseguita prima che i dati siano disponibili. Limitare la banda nella scheda Network impostando “Slow 3G” e ricaricare. Se l’errore compare in modo affidabile, si tratta di una race condition, e la correzione è aspettare i dati invece di proteggere la lettura.
La correzione che non è una correzione
user?.profile?.name fa sparire l’errore. Non fa comparire il profilo. L’optional chaining
trasforma un crash in un silenzioso undefined, che poi fluisce nella pagina come una stringa
vuota, una riga mancante, un pulsante senza etichetta, e a questo punto il bug è invisibile per chi
sviluppa e resta visibile per il cliente.
È lo strumento giusto quando l’assenza è legittima: un account che davvero non ha ancora un
profilo. È lo strumento sbagliato quando l’assenza è il bug. La domanda da porsi prima di
aggiungere un ?. è se quel valore dovrebbe mai poter mancare. Se la risposta è no, il crash stava
dicendo qualcosa, e una protezione è un modo per non ascoltare.
Cosa portarsi via
Il messaggio indica la proprietà richiesta e nasconde la cosa che era vuota. Va letto come “il passaggio prima di questo non ha prodotto nulla”, e bisogna andare a guardare proprio quel passaggio: la richiesta, il valore restituito, il nome della chiave, la lista che era vuota. La correzione si trova sempre a monte del numero di riga.
E quando la segnalazione arriva dallo schermo di qualcun altro, la riga della console e il log di rete non sono il contesto del bug. Per questo errore, sono il bug.