DanLevy.net

Quiz: Padronanza Avanzata degli Errori JS

Le tue eccezioni sono davvero eccezionali?

Pensi di conoscere a fondo gli errori JavaScript?

Cosa restituisce JSON.stringify(error)?

const error = new Error('Oops');
console.log(JSON.stringify(error));

Gli oggetti Error hanno proprietà non enumerabili (message, name, stack), quindi JSON.stringify() restituisce {}. Questo è un inconveniente comune quando si inviano errori nelle risposte API. Usa JSON.stringify(error, Object.getOwnPropertyNames(error)) o crea invece un oggetto semplice.

Quali proprietà vengono enumerate da JSON.stringify?

Qual è la differenza tra questi due?

const err = new Error('Test');
console.log(err);
console.log(JSON.stringify(err));

console.log(err) mostra l’errore con il suo messaggio e lo stack trace perché la console ha una gestione speciale per gli oggetti Error. JSON.stringify(err) restituisce '{}' perché le proprietà di Error non sono enumerabili. Questa differenza mette in difficoltà molti sviluppatori che debugano le API.

La console può visualizzare proprietà non enumerate da JSON.

Quali sono i risultati di questi controlli?

class CustomError extends Error {}
const err = new CustomError('test');
console.log(err instanceof CustomError);
console.log(err instanceof Error);
console.log(err instanceof Object);

Tutte e tre restituiscono true. CustomError estende Error, che estende Object. L’operatore instanceof controlla l’intera catena di prototipi, quindi un’istanza di CustomError è anche un’istanza di Error e Object.

Segui tutta la catena di prototipi.

Cosa succede con instanceof Error tra iframe?

console.log(iframeError instanceof Error);

Ogni realm, per esempio un iframe, ha il proprio costruttore Error, quindi instanceof può restituire false tra realm diversi. Object.prototype.toString.call(obj) riconosce molti errori tra realm, ma il tag può essere influenzato da Symbol.toStringTag: non è una prova infallibile del tipo.

I due contesti condividono lo stesso costruttore Error?

Cosa succede quando lanci una stringa?

try {
throw "Oops!";
} catch (e) {
console.log(e instanceof Error);
console.log(typeof e);
}

JavaScript consente di lanciare qualsiasi valore. Qui, e instanceof Error è false e typeof e è "string". Questo può rompere il codice di gestione degli errori che presume che tutte le eccezioni catturate siano oggetti Error. Lancia sempre istanze di Error per un debug migliore.

Il valore lanciato viene trasformato automaticamente?

Qual è il valore di err.name?

class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
const err = new CustomError('test');
console.log(err.name);

err.name è "CustomError" perché this.constructor.name restituisce il nome della classe. Impostare this.name = this.constructor.name è un pattern comune per garantire che le classi di errore personalizzate mostrino il nome corretto nelle tracce di stack e nei messaggi di errore.

Leggi l’assegnazione esplicita alla proprietà name.

Qual è l’output senza impostare name?

class MyError extends Error {
// No constructor or name setting
}
const err = new MyError('test');
console.log(err.name);

Se non imposti esplicitamente this.name, l’errore eredita la proprietà name predefinita dalla classe Error, che è "Error". Per questo le classi di errore personalizzate dovrebbero sempre impostare this.name = this.constructor.name nel loro costruttore.

Quale name viene ereditato dal prototipo?

Cosa restituisce wrapper.cause.message?

const original = new Error('Original error');
const wrapper = new Error('Wrapper',
{ cause: original }
);
console.log(wrapper.cause.message);

Error.cause (ES2022) consente di concatenare errori per preservare il contesto dell’errore originale. wrapper.cause fa riferimento all’errore originale, quindi wrapper.cause.message restituisce "Original error". Questo è utile per avvolgere errori di livello inferiore con un contesto di livello superiore.

Segui il riferimento contenuto in cause.

Cosa fa Error.captureStackTrace?

function createError(msg) {
const err = new Error(msg);
Error.captureStackTrace(err, createError);
return err;
}
const error = createError('test');

Error.captureStackTrace (V8/Node.js) rimuove la funzione specificata (createError) dalla traccia dello stack, rendendo le funzioni factory di errore invisibili agli utenti finali. Questo genera tracce dello stack più pulite che indicano il punto in cui la factory è stata chiamata, non la factory stessa.

Il secondo argomento indica il frame da escludere.

Qual è il messaggio di errore?

function validate(value) {
if (!value) {
throw new Error(
`Value ${value} is invalid`
);
}
}
try {
validate(undefined);
} catch (e) {
console.log(e.message);
}

I letterali template convertono undefined nella stringa "undefined" durante l’interpolazione. Il messaggio di errore diventa "Value undefined is invalid". Per messaggi più puliti, considera di usare value ?? 'null' o controlli simili prima dell’interpolazione.

Un template literal interpola anche undefined.

Cosa viene inviato al client?

// Express.js route
app.get('/api/data', (req, res) => {
const error = new Error('Database failed');
res.json({ error });
});

res.json() usa internamente JSON.stringify(), quindi l’oggetto Error diventa {}. Il client riceve {"error":{}}. Per risolvere, usa res.json({ error: error.message }) oppure res.json({ error: { message: error.message, name: error.name } }).

Considera come viene serializzato l’oggetto Error annidato.

Cosa può accettare Promise.reject()?

Promise.reject('string').catch(e =>
console.log(typeof e)
);
Promise.reject({code: 404}).catch(e =>
console.log(e.code)
);
Promise.reject(42).catch(e =>
console.log(e)
);

Come throw, Promise.reject() accetta qualsiasi valore – stringhe, oggetti, numeri, ecc. Questo stampa "string", 404 e 42. Controlla sempre il tipo dei valori catturati nelle catene di promise, soprattutto quando lavori con codice di terze parti che potrebbe rifiutare con valori non‑Error.

Un rifiuto è limitato alle istanze di Error?

Quanto sono affidabili error.code e error.errno?

const fs = require('fs');
fs.readFile('missing.txt', (err, data) => {
if (err) {
console.log(err.code); // 'ENOENT'
console.log(err.errno); // -2
}
});

Proprietà come code e errno sono specifiche dell’ambiente (Node.js in questo caso) e non fanno parte dell’oggetto Error standard. Gli errori del browser non avranno queste proprietà. Controlla sempre la loro esistenza: if (err.code === 'ENOENT') invece di presumere che esistano.

Distingui le proprietà standard da quelle dell’ambiente.

Cosa restituiscono questi controlli?

const fakeError = {
name: 'Error',
message: 'Fake error',
stack: 'fake stack'
};
console.log(fakeError instanceof Error);
console.log(Object.prototype.toString.call(
fakeError
) === '[object Error]');

instanceof Error è false: questo oggetto semplice non ha Error.prototype nella propria catena di prototipi. Object.prototype.toString.call(fakeError) restituisce [object Object], quindi anche il confronto con [object Error] è false. Il tag può essere influenzato da Symbol.toStringTag, dunque non va usato come prova infallibile del tipo.

Confronta la catena di prototipi con il tag dell’oggetto, influenzabile da Symbol.toStringTag.

Padroneggia l’Arte della Gestione degli Errori

Dalle trappole della serializzazione ai fallimenti di instanceof tra contesti diversi, questi concetti avanzati distinguono i junior dai professionisti davvero provati.

Pronto per altre sfide? Dai un’occhiata alla nostra raccolta completa di quiz per altri rompicapi su JavaScript, algoritmi e molto altro!