​​Vermeiden Sie diese verdammten "Cannot read property of undefined"-Fehler

Avatar of Adam Giese
Adam Giese am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

​​​​Uncaught TypeError: Cannot read property 'foo' of undefined.​ Der gefürchtete Fehler, auf den wir alle irgendwann in der JavaScript-Entwicklung stoßen. Es könnte ein leerer Zustand von einer API sein, die anders zurückgibt, als Sie erwartet haben. Es könnte etwas anderes sein. Wir wissen es nicht, weil der Fehler selbst so allgemein und breit ist.

​​Ich hatte kürzlich ein Problem, bei dem bestimmte Umgebungsvariablen aus dem einen oder anderen Grund nicht geladen wurden, was zu allerlei Komplikationen führte, während dieser Fehler direkt vor mir stand. Was auch immer die Ursache war, es kann ein katastrophaler Fehler sein, wenn er unberücksichtigt bleibt. Wie können wir ihn also von vornherein verhindern?

​​Lassen Sie es uns herausfinden.

Hilfsbibliothek

​​Wenn Sie bereits eine Hilfsbibliothek in Ihrem Projekt verwenden, besteht eine gute Chance, dass diese eine Funktion zur Vermeidung dieses Fehlers enthält. _.get​ in Lodash​ (Doku) oder R.path in Ramda​ (Doku) ermöglichen den sicheren Zugriff auf das Objekt.
​​
​​Wenn Sie bereits eine Hilfsbibliothek verwenden, ist dies wahrscheinlich die einfachste Lösung. Wenn Sie keine Hilfsbibliothek verwenden, lesen Sie weiter!

​​

Kurzschluss mit &&

​​​​Eine interessante Tatsache über logische Operatoren in JavaScript ist, dass sie nicht immer einen booleschen Wert zurückgeben. Laut Spezifikation ist „der von einem &&​ oder ||​ Operator erzeugte Wert nicht notwendigerweise vom Typ Boolean. Der erzeugte Wert ist immer der Wert eines der beiden Operanden-Ausdrücke.“
​​
​​​​Im Fall des &&​ Operators wird der erste Ausdruck verwendet, wenn er ein „falsy“ Wert ist. Andernfalls wird der zweite Ausdruck verwendet. Das bedeutet, dass der Ausdruck 0 && 1​ als 0​ (ein falsy Wert) ausgewertet wird und der Ausdruck 2 && 3​ als 3​ ausgewertet wird. Wenn mehrere &&​ Ausdrücke verkettet sind, werden sie entweder zum ersten falsy Wert oder zum letzten Wert ausgewertet. Zum Beispiel wird 1 && 2 && 3 && null && 4​ als null​ ausgewertet, und 1 && 2 && 3​ wird als 3​ ausgewertet.

​​​​Wie ist das nützlich für den sicheren Zugriff auf verschachtelte Objekteigenschaften? Logische Operatoren in JavaScript „kurzschließen“. Im Fall von &&​ bedeutet dies, dass der Ausdruck nach Erreichen des ersten falsy Wertes nicht weiter fortgesetzt wird.

​​​​

​​const foo = false && destroyAllHumans();
​​console.log(foo); // false, and humanity is safe

​​In diesem Beispiel wird destroyAllHumans niemals aufgerufen, da der &&​ Operand die gesamte Auswertung nach false​ gestoppt hat.

​​Dies kann verwendet werden, um sicher auf verschachtelte Eigenschaften zuzugreifen.

​​

​​const meals = {
​​  breakfast: null, // I skipped the most important meal of the day! :(
​​  lunch: {
​​    protein: 'Chicken',
​​    greens: 'Spinach',
​​  },
​​  dinner: {
​​    protein: 'Soy',
​​    greens: 'Kale',
​​  },
​​};
​​
​​const breakfastProtein = meals.breakfast && meals.breakfast.protein; // null
​​const lunchProtein = meals.lunch && meals.lunch.protein; // 'Chicken'

​​Neben seiner Einfachheit ist einer der Hauptvorteile dieses Ansatzes seine Kürze bei der Handhabung kleiner Ketten. Beim Zugriff auf tiefere Objekte kann dies jedoch ziemlich wortreich sein.

​​

const favorites = {
​​  video: {
​​    movies: ['Casablanca', 'Citizen Kane', 'Gone With The Wind'],
​​    shows: ['The Simpsons', 'Arrested Development'],
​​    vlogs: null,
​​  },
​​  audio: {
​​    podcasts: ['Shop Talk Show', 'CodePen Radio'],
​​    audiobooks: null,
​​  },
​​  reading: null, // Just kidding -- I love to read
​​};
​​
​​const favoriteMovie = favorites.video && favorites.video.movies && favorites.video.movies[0];
​​// Casablanca
​​const favoriteVlog = favorites.video && favorites.video.vlogs && favorites.video.vlogs[0];
​​// null

​​Je tiefer ein Objekt verschachtelt ist, desto unhandlicher wird es.

​​
​​

Das „Maybe Monad“

​​Oliver Steele hat sich diese Methode ausgedacht und sie in seinem Blogbeitrag „Monads on the Cheap I: The Maybe Monad.“ ausführlicher behandelt. Ich werde versuchen, hier eine kurze Erklärung zu geben.

​​

const favoriteBook = ((favorites.reading||{}).books||[])[0]; // undefined
​​const favoriteAudiobook = ((favorites.audio||{}).audiobooks||[])[0]; // undefined
​​const favoritePodcast = ((favorites.audio||{}).podcasts||[])[0]; // 'Shop Talk Show'

​​Ähnlich wie im obigen Kurzschlussbeispiel prüft diese Methode, ob ein Wert falsy ist. Wenn ja, wird versucht, die nächste Eigenschaft eines leeren Objekts abzurufen. Im obigen Beispiel ist favorites.reading​ null​, daher wird die books​ Eigenschaft von einem leeren Objekt abgerufen. Dies führt zu einem undefined​, und so wird 0​ ebenfalls von einem leeren Array abgerufen.

​​Der Vorteil dieser Methode gegenüber der &&​ Methode ist, dass sie die Wiederholung von Eigenschaftsnamen vermeidet. Bei tieferen Objekten kann dies ein erheblicher Vorteil sein. Der Hauptnachteil wäre die Lesbarkeit – es ist kein gängiges Muster und erfordert vom Leser möglicherweise einen Moment, um zu verstehen, wie es funktioniert.

​​

try/catch

​​try...catch​ Anweisungen in JavaScript bieten eine weitere Methode zum sicheren Zugriff auf Eigenschaften.

​​

try {
​​  console.log(favorites.reading.magazines[0]);
​​} catch (error) {
​​  console.log("No magazines have been favorited.");
​​}

​​Leider sind try...catch​ Anweisungen in JavaScript keine Ausdrücke. Sie werten keinen Wert aus, wie sie es in einigen Sprachen tun. Dies verhindert eine prägnante try​ Anweisung als Möglichkeit, eine Variable zu setzen.

​​Eine Option ist die Verwendung einer let​ Variable, die im Block über der try...catch​ definiert ist.

​​

let favoriteMagazine;
​​try { 
​​  favoriteMagazine = favorites.reading.magazines[0]; 
​​} catch (error) { 
​​  favoriteMagazine = null; /* any default can be used */
​​};

​​Obwohl es wortreich ist, funktioniert dies zum Setzen einer einzelnen Variablen (d. h. wenn die veränderliche Variable Sie nicht abschreckt). Probleme können jedoch auftreten, wenn sie in großer Menge durchgeführt werden.

​​

let favoriteMagazine, favoriteMovie, favoriteShow;
​​try {
​​  favoriteMovie = favorites.video.movies[0];
​​  favoriteShow = favorites.video.shows[0];
​​  favoriteMagazine = favorites.reading.magazines[0];
​​} catch (error) {
​​  favoriteMagazine = null;
​​  favoriteMovie = null;
​​  favoriteShow = null;
​​};
​​
​​console.log(favoriteMovie); // null
​​console.log(favoriteShow); // null
​​console.log(favoriteMagazine); // null

​​Wenn einer der Zugriffsversuche auf die Eigenschaft fehlschlägt, fallen alle auf ihre Standardwerte zurück.

​​Eine Alternative ist, die try...catch​ in eine wiederverwendbare Hilfsfunktion zu verpacken.

​​

const tryFn = (fn, fallback = null) => {
​​  try {
​​    return fn();
​​  } catch (error) {
​​    return fallback;
​​  }
​​} 
​​
​​const favoriteBook = tryFn(() => favorites.reading.book[0]); // null
​​const favoriteMovie = tryFn(() => favorites.video.movies[0]); // "Casablanca"

​​Durch das Verpacken des Zugriffs auf das Objekt in einer Funktion können Sie den „unsicheren“ Code verzögern und ihn in eine try...catch​ übergeben.

​​Ein Hauptvorteil dieser Methode ist, wie natürlich der Zugriff auf die Eigenschaft ist. Solange Eigenschaften in eine Funktion verpackt sind, werden sie sicher abgerufen. Ein Standardwert kann auch im Falle eines nicht existierenden Pfades angegeben werden.

Zusammenführen mit einem Standardobjekt

​​
Durch das Zusammenführen eines Objekts mit einem ähnlich geformten Objekt mit „Standards“ können wir sicherstellen, dass der Pfad, den wir abzurufen versuchen, sicher ist.
​​
​​

const defaults = {
​​  position: "static",
​​  background: "transparent",
​​  border: "none",
​​};
​​
​​const settings = {
​​  border: "1px solid blue",
​​};
​​
​​const merged = { ...defaults, ...settings };
​​
​​console.log(merged); 
​​/*
​​  {
​​    position: "static",
​​    background: "transparent",
​​    border: "1px solid blue"
​​  }
​​*/

​​
​​Seien Sie jedoch vorsichtig, da das gesamte verschachtelte Objekt überschrieben werden kann, anstatt einer einzelnen Eigenschaft.
​​
​​

const defaults = {
​​  font: {
​​    family: "Helvetica",
​​    size: "12px",
​​    style: "normal",
​​  },        
​​  color: "black",
​​};
​​
​​const settings = {
​​  font: {
​​    size: "16px",
​​  }
​​};
​​
​​const merged = { 
​​  ...defaults, 
​​  ...settings,
​​};
​​
​​console.log(merged.font.size); // "16px"
​​console.log(merged.font.style); // undefined

​​Oh nein! Um dies zu beheben, müssen wir ähnlich jedes der verschachtelten Objekte kopieren.

​​

const merged = { 
​​  ...defaults, 
​​  ...settings,
​​  font: {
​​    ...defaults.font,
​​    ...settings.font,
​​  },
​​};
​​
​​console.log(merged.font.size); // "16px"
​​console.log(merged.font.style); // "normal"

​​Viel besser!

​​Dieses Muster ist üblich bei Plugins oder Komponenten, die ein großes Einstellungs-Objekt mit integrierten Standardwerten akzeptieren.

​​Ein Bonus dieser Vorgehensweise ist, dass wir durch das Schreiben eines Standardobjekts eine Dokumentation darüber erstellen, wie ein Objekt aussehen sollte. Leider kann je nach Größe und Struktur der Daten das „Zusammenführen“ durch das Kopieren jedes verschachtelten Objekts unübersichtlich werden.

​​​

Die Zukunft: Optional Chaining

​​Derzeit gibt es einen TC39-Vorschlag für eine Funktion namens „Optional Chaining“. Dieser neue Operator würde so aussehen

​​console.log(favorites?.video?.shows[0]); // 'The Simpsons'
​​console.log(favorites?.audio?.audiobooks[0]); // undefined

​​Der ?.​ Operator funktioniert durch Kurzschluss: Wenn die linke Seite des ?.​ Operators zu null​ oder undefined​ ausgewertet wird, wird der gesamte Ausdruck zu undefined​ ausgewertet und die rechte Seite bleibt unerwogen.

​​Um einen benutzerdefinierten Standardwert zu haben, können wir den ||​ Operator im Falle eines undefined​ verwenden.

​​

console.log(favorites?.audio?.audiobooks[0] || "The Hobbit");

​​

Welche Methode sollten Sie verwenden?

​​Die Antwort, wie Sie vielleicht erraten haben, ist die alte Antwort… „es kommt darauf an“. Wenn der Optional Chaining Operator zur Sprache hinzugefügt wurde und die erforderliche Browserunterstützung vorhanden ist, ist dies wahrscheinlich die beste Option. Wenn Sie jedoch nicht aus der Zukunft stammen, gibt es mehr zu berücksichtigen. Verwenden Sie eine Hilfsbibliothek? Wie tief ist Ihr Objekt verschachtelt? Müssen Sie Standardwerte angeben? Unterschiedliche Fälle können einen anderen Ansatz erfordern.