Exception Types
Node.js exceptions are primarily divided into two categories:
- Synchronous Exceptions: Typically triggered by
throwstatements. - Asynchronous Exceptions: Caused by failures in asynchronous operations (e.g., file I/O, network requests).
Exception Objects
In Node.js, exceptions are encapsulated as Error objects, which include detailed information such as the error name, message, and stack trace. Common Error subclasses include:
Error: The base error class.RangeError: Thrown when a value exceeds its valid range.ReferenceError: Thrown when referencing an undefined variable.SyntaxError: Thrown for code syntax errors.TypeError: Thrown when an operand type is incorrect.URIError: Thrown for URI encoding or decoding errors.EvalError: Thrown by theeval()function.AggregateError: Represents a collection of errors.InternalError: Internal errors, such as memory exhaustion.
Exception Handling
Synchronous Exception Handling
Use try...catch statements to handle synchronous exceptions.
try {
// Code that may throw an exception
throw new Error('An error occurred');
} catch (err) {
console.error('Caught an exception:', err.message);
}Asynchronous Exception Handling
Asynchronous exceptions are typically handled in callback functions.
fs.readFile('nonexistentfile.txt', (err, data) => {
if (err) {
console.error('File read error:', err);
return;
}
// Process file data
});Asynchronous Error Handling
Promise Error Handling
Promises provide an elegant way to handle asynchronous errors.
Promise.reject(new Error('Promise rejected'))
.catch(err => console.error('Promise error:', err));Async/Await Error Handling
async/await is syntactic sugar for Promises, simplifying asynchronous error handling.
async function loadFile() {
try {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('File load error:', err);
}
}Global Exception Handling
uncaughtException Event
Listen to the uncaughtException event on the process object to capture unhandled exceptions.
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
// Log errors, send reports, etc.
});unhandledRejection Event
Listen to the unhandledRejection event to capture unhandled Promise rejections.
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});Custom Error Codes
Custom error codes can be defined to differentiate error types, aiding in error handling and debugging.
class CustomError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = this.constructor.name;
}
}
try {
throw new CustomError('Custom error message', 500);
} catch (err) {
if (err instanceof CustomError) {
console.error('Custom Error:', err.message, 'Code:', err.code);
}
}Error Logging and Reporting
In production environments, detailed error logs should be recorded and error reports sent for tracking and resolution.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log' })
],
});
logger.error('An error occurred', { error: err });Performance Optimization
CPU Optimization
Avoiding Blocking Calls
Node.js excels with its non-blocking I/O model. Avoid blocking calls like fs.readFileSync and use asynchronous alternatives like fs.readFile.
Reducing Compute-Intensive Tasks
Break compute-intensive tasks into smaller chunks, leveraging the event loop’s idle time to avoid prolonged CPU usage.
Using Worker Threads
Since Node.js v10.5.0, the Worker Threads API enables parallel JavaScript execution outside the main thread.
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (result) => {
console.log(result);
});
} else {
const result = /* perform heavy computation */;
parentPort.postMessage(result);
}Memory Optimization
Avoiding Memory Leaks
- Circular References: Ensure unused objects are garbage-collected.
- Timers and Event Listeners: Clear timers and event listeners when no longer needed.
Caching Strategies
Use caching to reduce redundant computations and I/O, but manage cache size and expiration to avoid excessive memory usage.
Compression and Serialization
Use compression algorithms (e.g., gzip) and efficient serialization formats (e.g., protobuf) to minimize memory and network usage.
Asynchronous Operation Optimization
Avoiding Callback Hell
Use Promises and async/await instead of nested callbacks for clearer, maintainable code.
Controlling Concurrency
Limit concurrent operations to prevent resource exhaustion, using Promise.allSettled or custom concurrency controllers.
function processItems(items, handler, maxConcurrency) {
const results = [];
let active = 0;
let next = 0;
const loop = () => {
while (next < items.length && active < maxConcurrency) {
active++;
const item = items[next++];
handler(item)
.then(result => {
results.push(result);
active--;
if (active < maxConcurrency && next < items.length) {
loop();
}
})
.catch(err => {
// Handle error
});
}
};
loop().then(() => {
// All items processed
});
}Using the Cluster Module for Multi-Process Handling
Cluster Module Overview
The Cluster module creates child processes, each with its own event loop and memory space, leveraging multi-core CPUs.
Creating a Master Process
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
});
} else {
// Workers can share any TCP connection
// In this case, it is an HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}Advanced Optimization Strategies
Using V8 Performance Tools
The V8 engine provides tools like CPU profiler, heap profiler, and timeline profiler to identify performance bottlenecks.
# Start CPU profiler
node --trace-turbo-inlining --trace-deopt --trace-opt --trace-gc --trace-ic --expose-gc yourapp.js
# Generate heap snapshot
node --expose-gc yourapp.js > heap-snapshot.heapsnapshotAnalyze .heapsnapshot files in Chrome DevTools to inspect memory usage.
JIT Compilation Optimization
Understand V8’s Just-In-Time (JIT) compiler mechanisms, such as inline caching (ICs), type feedback, and optimizing compilation, to write more efficient code.
Reducing GC Pressure
Frequent garbage collection impacts performance. Reduce object allocations, use weak references, and clear unused objects to lower GC frequency.
Network Optimization
Using Keep-Alive
HTTP Keep-Alive reuses TCP connections, reducing connection setup and teardown overhead.
Compressing Responses
Use gzip or Brotli to compress response data, minimizing network transfer size.
Caching Strategies
Set HTTP cache headers like Cache-Control and Expires to reduce unnecessary requests.
File System Optimization
Asynchronous Operations
Always use asynchronous file system methods (e.g., fs.readFile, fs.writeFile) to avoid blocking the event loop.
Streaming Operations
Use streaming interfaces (e.g., fs.createReadStream, fs.createWriteStream) for large files to avoid loading them entirely into memory.
Database Optimization
Connection Pooling
Use connection pools to manage database connections, avoiding frequent connection establishment and termination.
Query Optimization
Optimize SQL queries with indexes to avoid full table scans.
ORM vs. Raw SQL
Choose between ORM and raw SQL based on needs; ORMs offer convenience but may introduce performance overhead.
Performance Monitoring and Tuning
Using PM2
PM2 is a robust process manager that monitors application health, offering features like restarts, logging, and load balancing.
New Relic / Datadog
Use professional APM tools like New Relic or Datadog to monitor performance metrics, including CPU, memory, network, and database activity.



