[2026] Node.js & JavaScript Error Handling Best Practices | try/catch & async

[2026] Node.js & JavaScript Error Handling Best Practices | try/catch & async

이 글의 핵심

Error handling best practices for Node.js and JavaScript: try/catch/finally, custom errors, Promise and async/await failures, Express middleware, and structured logging for production APIs.

Introduction

Error handling deals with exceptional conditions that occur while your program runs.

1. try-catch-finally

Basics

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

try {
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("에러 발생:", error.message);
} finally {
    console.log("정리 작업");
}

Practical example

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function divide(a, b) {
    if (b === 0) {
        throw new Error("0으로 나눌 수 없습니다");
    }
    return a / b;
}
try {
    console.log(divide(10, 2));  // 5
    console.log(divide(10, 0));  // Error!
    console.log("이 줄은 실행 안 됨");
} catch (error) {
    console.error("에러:", error.message);
} finally {
    console.log("계산 완료");
}

Nested try-catch

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

try {
    try {
        throw new Error("내부 에러");
    } catch (innerError) {
        console.log("내부 처리:", innerError.message);
        throw new Error("외부 에러");
    }
} catch (outerError) {
    console.log("외부 처리:", outerError.message);
}

2. The Error object

Built-in error types

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Error: generic
throw new Error("일반 에러");
// SyntaxError
try {
    eval("{ invalid json");
} catch (e) {
    console.log(e.name);  // SyntaxError
}
// ReferenceError
try {
    console.log(nonExistent);
} catch (e) {
    console.log(e.name);  // ReferenceError
}
// TypeError
try {
    null.toString();
} catch (e) {
    console.log(e.name);  // TypeError
}
// RangeError
try {
    new Array(-1);
} catch (e) {
    console.log(e.name);  // RangeError
}

Error properties

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

try {
    throw new Error("테스트 에러");
} catch (error) {
    console.log(error.name);     // Error
    console.log(error.message);  // 테스트 에러
    console.log(error.stack);    // stack trace
}

3. Custom errors

Custom error classes

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}
class NetworkError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = "NetworkError";
        this.statusCode = statusCode;
    }
}
function validateAge(age) {
    if (typeof age !== 'number') {
        throw new ValidationError("나이는 숫자여야 합니다");
    }
    if (age < 0 || age > 150) {
        throw new ValidationError("나이는 0-150 사이여야 합니다");
    }
    return true;
}
try {
    validateAge("25");
} catch (error) {
    if (error instanceof ValidationError) {
        console.error("유효성 에러:", error.message);
    } else {
        console.error("알 수 없는 에러:", error);
    }
}

Handling multiple error types

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class DatabaseError extends Error {
    constructor(message, query) {
        super(message);
        this.name = "DatabaseError";
        this.query = query;
    }
}
function processData(data) {
    try {
        if (!data) {
            throw new ValidationError("데이터가 없습니다");
        }
        
        if (data.age < 0) {
            throw new ValidationError("나이는 양수여야 합니다");
        }
        
        return data;
    } catch (error) {
        if (error instanceof ValidationError) {
            console.error("유효성 에러:", error.message);
        } else if (error instanceof DatabaseError) {
            console.error("DB 에러:", error.message, error.query);
        } else {
            console.error("알 수 없는 에러:", error);
        }
        throw error;
    }
}

4. Asynchronous errors

Promises

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// .catch()
fetch("https://api.example.com/data")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error("에러:", error));
// Error mid-chain
Promise.resolve(1)
    .then(x => {
        throw new Error("에러!");
    })
    .then(x => console.log(x))
    .catch(error => console.error(error.message))
    .then(() => console.log("복구됨"));
// Multiple promises
Promise.all([
    fetch("/api/users"),
    fetch("/api/posts")
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log(data))
.catch(error => console.error("하나라도 실패:", error));

async/await

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function fetchData() {
    try {
        const response = await fetch("https://api.example.com/data");
        
        if (!response.ok) {
            throw new NetworkError(`HTTP ${response.status}`, response.status);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("에러:", error.message);
        return null;
    }
}
async function complexOperation() {
    try {
        const data = await fetchData();
        const result = processData(data);
        return result;
    } catch (error) {
        if (error instanceof NetworkError) {
            console.error("네트워크 에러:", error.statusCode);
        } else if (error instanceof ValidationError) {
            console.error("유효성 에러:", error.message);
        } else {
            console.error("알 수 없는 에러:", error);
        }
        throw error;
    }
}

5. Practical patterns

Pattern 1: Result wrapper

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Result {
    constructor(success, data, error) {
        this.success = success;
        this.data = data;
        this.error = error;
    }
    
    static ok(data) {
        return new Result(true, data, null);
    }
    
    static fail(error) {
        return new Result(false, null, error);
    }
}
async function fetchUserSafe(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        const user = await response.json();
        return Result.ok(user);
    } catch (error) {
        return Result.fail(error.message);
    }
}
const result = await fetchUserSafe(1);
if (result.success) {
    console.log("데이터:", result.data);
} else {
    console.error("에러:", result.error);
}

Pattern 2: Retry with backoff

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function retry(fn, maxRetries = 3, delay = 1000) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === maxRetries - 1) {
                throw error;
            }
            console.log(`재시도 ${i + 1}/${maxRetries}`);
            await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
        }
    }
}
retry(() => fetch("https://api.example.com/data"))
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error("최종 실패:", error));

Pattern 3: Error logging

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class ErrorLogger {
    static log(error, context = {}) {
        const errorInfo = {
            name: error.name,
            message: error.message,
            stack: error.stack,
            timestamp: new Date().toISOString(),
            ...context
        };
        
        console.error("에러 로그:", JSON.stringify(errorInfo, null, 2));
        
        // Send to server
        // fetch('/api/errors', { method: 'POST', body: JSON.stringify(errorInfo) });
    }
}
try {
    throw new Error("테스트 에러");
} catch (error) {
    ErrorLogger.log(error, { userId: 123, action: "데이터 로드" });
}

6. Practical example: API client

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        
        try {
            const response = await fetch(url, options);
            
            if (!response.ok) {
                throw new NetworkError(
                    `HTTP ${response.status}: ${response.statusText}`,
                    response.status
                );
            }
            
            const data = await response.json();
            return Result.ok(data);
        } catch (error) {
            if (error instanceof NetworkError) {
                console.error("네트워크 에러:", error.message);
            } else if (error instanceof SyntaxError) {
                console.error("JSON 파싱 에러:", error.message);
            } else {
                console.error("알 수 없는 에러:", error);
            }
            return Result.fail(error.message);
        }
    }
    
    async get(endpoint) {
        return this.request(endpoint);
    }
    
    async post(endpoint, body) {
        return this.request(endpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body)
        });
    }
}
const api = new ApiClient("https://api.example.com");
const result = await api.get("/users/1");
if (result.success) {
    console.log("사용자:", result.data);
} else {
    console.error("에러:", result.error);
}

Summary

Key takeaways

  1. try-catch-finally: structured error handling
  2. throw: signal failure
  3. Error object: name, message, stack
  4. Custom errors: class extends Error
  5. Async: .catch() or try/catch with async/await

Tips

  • Clear messages: make failures easy to diagnose
  • Branch by type: use instanceof (or error codes) for control flow
  • Re-throw: propagate with throw error when you cannot handle
  • Logging: record enough context for production debugging

Next steps


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3