When the program crashes, a stack trace is printed. This stack trace comes from the error object we created straight after using the throw
keyword. The Error
constructor is native to JavaScript, and takes a string as the Error message, while auto generating a stack trace when created.
function doTask (amount) {
if (typeof amount !== 'number') throw new Error('amount must be a number')
return amount / 2
}
There are six other native error constructors that inherit from the base Error constructor, these are:
- EvalError
- SyntaxError
- RangeError
- ReferenceError
- TypeError
- URIError
function doTask (amount) {
if (typeof amount !== 'number') throw new TypeError('amount must be a number')
if (amount <= 0) throw new RangeError('amount must be greater than zero')
return amount / 2
}
We can inherit from Error ourselves to create a custom error instance for a particular use case.
class OddError extends Error {
constructor (varName = '') {
super(varName + ' must be even')
this.code = 'ERR_MUST_BE_EVEN'
}
get name () {
return 'OddError [' + this.code + ']'
}
}
When an error is thrown in a normal synchronous function it can be handled with a try/catch block.
try {
const result = doTask(3)
console.log('result', result)
} catch (err) {
console.error('Error caught: ', err)
}
Promises must use the catch method to catch rejections
doTask(3)
.then((result) => {
console.log('result', result)
})
.catch((err) => {
if (err.code === 'ERR_AMOUNT_MUST_BE_NUMBER') {
console.error('wrong type')
} else if (err.code === 'ERRO_AMOUNT_MUST_EXCEED_ZERO') {
console.error('out of range')
} else if (err.code === 'ERR_MUST_BE_EVEN') {
console.error('cannot be odd')
} else {
console.error('Unknown error', err)
}
})
The async/await syntax supports try/catch of rejections.
async function run () {
try {
const result = await doTask(3)
console.log('result', result)
} catch (err) {
if (err instanceof TypeError) {
console.error('wrong type')
} else if (err instanceof RangeError) {
console.error('out of range')
} else if (err.code === 'ERR_MUST_BE_EVEN') {
console.error('cannot be odd')
} else {
console.error('Unknown error', err)
}
}
}
run()
Error propagation is where, instead of handling the error, we make it the responsibility of the caller instead.
class OddError extends Error {
constructor (varName = '') {
super(varName + ' must be even')
this.code = 'ERR_MUST_BE_EVEN'
}
get name () {
return 'OddError [' + this.code + ']'
}
}
function codify (err, code) {
err.code = code
return err
}
async function doTask (amount) {
if (typeof amount !== 'number') throw codify(
new TypeError('amount must be a number'),
'ERR_AMOUNT_MUST_BE_NUMBER'
)
if (amount <= 0) throw codify(
new RangeError('amount must be greater than zero'),
'ERR_AMOUNT_MUST_EXCEED_ZERO'
)
if (amount % 2) throw new OddError('amount')
throw Error('some other error')
return amount/2
}
async function run () {
try {
const result = await doTask(4)
console.log('result', result)
} catch (err) {
if (err.code === 'ERR_AMOUNT_MUST_BE_NUMBER') {
throw Error('wrong type')
} else if (err.code === 'ERRO_AMOUNT_MUST_EXCEED_ZERO') {
throw Error('out of range')
} else if (err.code === 'ERR_MUST_BE_EVEN') {
throw Error('cannot be odd')
} else {
throw err
}
}
}
run().catch((err) => { console.error('Error caught', err) })
Most asynchronous methods exposed by the Node.js core API follow an idiomatic pattern referred to as an error-first callback. With this pattern, a callback function is passed to the method as an argument. When the operation either completes or an error is raised, the callback function is called with the Error object (if any) passed as the first argument. If no error was raised, the first argument will be passed as null.
const fs = require('fs');
fs.readFile('a file that does not exist', (err, data) => {
if (err) {
console.error('There was an error reading the file!', err);
return;
}
// Otherwise handle the data
});
The JavaScript try…catch mechanism cannot be used to intercept errors generated by asynchronous APIs. A common mistake for beginners is to try to use throw inside an error-first callback:
When an asynchronous method is called on an object that is an EventEmitter, errors can be routed to that object's 'error' event.
const net = require('net');
const connection = net.connect('localhost');
// Adding an 'error' event handler to a stream:
connection.on('error', (err) => {
// If the connection is reset by the server, or if it can't
// connect at all, or on any sort of error encountered by
// the connection, the error will be sent here.
console.error(err);
});
connection.pipe(process.stdout);
For all EventEmitter objects, if an 'error' event handler is not provided, the error will be thrown, causing the Node.js process to report an uncaught exception and crash Errors generated in this way cannot be intercepted using try…catch as they are thrown after the calling code has already exited.