Rilasciato · in miglioramento
Guida a JavaScript · 5/6
Per ora questo capitolo è disponibile solo in inglese.
Sooner or later every program meets bad input, a dropped connection or a missing file. This chapter covers throwing and catching errors, the built-in error types, custom errors, error handling in async code, and testing with the runner built into Node.js.
Signal a problem with throw and handle it with try...catch. A finally block runs whether or not anything failed, which makes it the right place for cleanup. Always throw Error objects rather than strings: they carry a stack that tells you where things went wrong.
function parseAge(input) {
const age = Number(input);
if (!Number.isInteger(age) || age < 0) {
throw new RangeError(`Not a valid age: ${input}`);
}
return age;
}
try {
console.log(parseAge("29")); // 29
console.log(parseAge("-3")); // throws here
console.log("never reached");
} catch (error) {
console.error(error.name, error.message); // RangeError Not a valid age: -3
} finally {
console.log("done checking");
}The engine and built-in functions throw different subclasses of Error, which you can tell apart with instanceof.
| Type | Typical cause |
|---|---|
TypeError | Reading a property of undefined, calling a non-function |
ReferenceError | Using a variable that was never declared |
RangeError | A value outside the allowed range, e.g. new Array(-1) |
SyntaxError | Invalid code, or bad input to JSON.parse |
Application-specific failures are easier to handle as subclasses of Error. When you catch one error and throw another, pass the original as cause so the root problem is not lost.
class NotFoundError extends Error {
constructor(resource, options) {
super(`${resource} not found`, options);
this.name = "NotFoundError";
this.resource = resource;
}
}
function loadConfig(text) {
try {
return JSON.parse(text);
} catch (error) {
throw new Error("Could not read config", { cause: error });
}
}
try {
throw new NotFoundError("user 42");
} catch (error) {
if (error instanceof NotFoundError) {
console.log("respond with 404:", error.message);
} else {
throw error; // not ours to handle
}
}
try {
loadConfig("{ broken");
} catch (error) {
console.log(error.message, "caused by", error.cause.name); // ... SyntaxError
}Catching everything and carrying on silently hides bugs. Catch what you can actually handle and rethrow the rest.
try...catch only sees a rejected Promise if you await it; without await, the rejection slips past. Node.js terminates the process on an unhandled rejection by default, so always handle them.
import { readFile } from "node:fs/promises";
async function readJson(path) {
try {
return JSON.parse(await readFile(path, "utf8"));
} catch (error) {
if (error.code === "ENOENT") return null; // missing file is fine
throw error;
}
}
// with a then chain, attach .catch
readJson("settings.json")
.then((data) => console.log(data ?? "no settings"))
.catch((error) => console.error("read failed:", error));
// see every outcome, success or failure
const results = await Promise.allSettled([readJson("a.json"), readJson("b.json")]);
for (const r of results) {
console.log(r.status, r.status === "fulfilled" ? r.value : r.reason.message);
}An error thrown inside a setTimeout callback also escapes a surrounding try...catch, because the callback runs later on its own. Chapter 6 explains why.
Node.js ships a test runner, node:test, and an assertion module, node:assert. Name files like *.test.js and node --test finds and runs them.
// age.test.js
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { parseAge } from "./age.js";
describe("parseAge", () => {
it("converts numeric strings", () => {
assert.equal(parseAge("29"), 29);
});
it("rejects negative numbers", () => {
assert.throws(() => parseAge("-1"), RangeError);
});
it("handles async failures", async () => {
await assert.rejects(Promise.reject(new Error("boom")), /boom/);
});
it("compares objects deeply", () => {
assert.deepEqual({ a: [1, 2] }, { a: [1, 2] });
});
});node --test
node --test --watchFor richer features, the community favorites are Vitest, which is fast and pairs naturally with Vite, and Jest, which has years of documentation behind it. Both use a similar describe / it / expect style.
// sum.test.js (Vitest)
import { describe, it, expect } from "vitest";
import sum from "./math.js";
describe("sum", () => {
it("adds every argument", () => {
expect(sum(1, 2, 3)).toBe(6);
});
it("returns 0 with no arguments", () => {
expect(sum()).toBe(0);
});
});Error objects and catch them only where you can handle them.finally; preserve root causes with cause.await plus try...catch, or with .catch().node:test and node:assert/strict cover a lot; reach for Vitest or Jest when you need more.
0 commenti
Accedi · Accedi per lasciare un commento.
Scrivi tu il primo commento.