프로그래밍 언어별 흔한 에러 해결 가이드 | C++, Python, Java, JavaScript

이 글의 핵심

에러 메시지는 버그 자체가 아니라 컴파일러나 런타임이 문제 위치를 알려 주는 힌트입니다. 이 글은 네 언어에서 입문자가 가장 자주 만나는 에러를 메시지 읽는 법부터 짚고, 처리되지 않은 Promise 거부 같은 비동기 에러까지 다룬 뒤, 컴파일·런타임·논리 에러별로 확인할 곳을 나누는 디버깅 절차와 도구 비교를 제시합니다.

들어가며: 에러는 배움의 기회

프로그래밍을 하다 보면 수많은 에러를 만나게 됩니다. 에러는 버그가 아니라 컴파일러나 런타임이 보내는 힌트입니다.

에러 메시지를 읽는 요령은 언어와 무관하게 비슷합니다. 먼저 에러의 종류(TypeError, NullPointerException, undefined reference)를 보고 어느 단계에서 실패했는지 판단합니다. 컴파일러나 링커가 낸 에러라면 코드가 실행조차 되지 않은 것이고, 런타임 예외라면 실행 중 특정 값 때문에 실패한 것입니다. 그다음 스택 트레이스에서 내 코드가 처음 등장하는 줄을 찾습니다. 맨 위 줄은 표준 라이브러리 내부인 경우가 많아서, 위에서부터 내려가며 내 파일 이름이 처음 보이는 곳이 실제로 고칠 위치일 가능성이 높습니다. 아래에서는 네 언어에서 가장 자주 보는 에러를 이 관점으로 정리합니다.


C++ 흔한 에러

Segmentation Fault (세그멘테이션 폴트)

에러 메시지:

Segmentation fault (core dumped)

원인:

  • 널 포인터 역참조
  • 배열 범위 초과
  • 해제된 메모리 접근
  • 스택 오버플로 예제:
// 원인 1: 널 포인터 역참조
int* ptr = nullptr;
*ptr = 10;  // ❌ Segmentation fault
// 해결
if (ptr != nullptr) {
    *ptr = 10;
}
// 원인 2: 배열 범위 초과
int arr[5] = {1, 2, 3, 4, 5};
arr[10] = 100;  // ❌ 미정의 동작, Segfault 가능
// 해결: vector 사용 (at()으로 범위 체크)
std::vector<int> vec = {1, 2, 3, 4, 5};
try {
    vec.at(10) = 100;  // ✅ 예외 발생
} catch (const std::out_of_range& e) {
    std::cerr << "범위 초과: " << e.what() << std::endl;
}
// 원인 3: 댕글링 포인터 (해제된 메모리 접근)
int* ptr2 = new int(10);
delete ptr2;
*ptr2 = 20;  // ❌ 이미 해제된 메모리
// 해결: 스마트 포인터 사용
std::unique_ptr<int> smart_ptr = std::make_unique<int>(10);
// 자동 해제, 댕글링 포인터 방지

디버깅 도구:

# GDB로 디버깅
g++ -g main.cpp -o main
gdb ./main
(gdb) run
(gdb) backtrace  # 크래시 위치 확인
# Valgrind로 메모리 에러 검사
valgrind --leak-check=full ./main
# AddressSanitizer (ASan)
g++ -fsanitize=address -g main.cpp -o main
./main

세 원인 중 널 포인터 역참조만 확실히 세그폴트를 냅니다. 주소 0 근처는 운영체제가 일부러 매핑하지 않기 때문입니다. 반면 arr[10] = 100이나 해제된 메모리 쓰기는 그 주소가 여전히 프로세스의 유효한 메모리 안에 있으면 아무 일 없이 지나가고, 대신 옆에 있던 다른 변수나 힙 관리 정보를 망가뜨립니다. 그래서 실제 크래시는 한참 뒤 전혀 상관없어 보이는 곳(free() 내부, 다른 객체의 소멸자)에서 납니다. 이런 경우 크래시 지점의 백트레이스는 피해 위치일 뿐이라, ASan처럼 잘못된 접근이 일어난 바로 그 순간을 잡아 주는 도구가 필요합니다. ASan은 heap-use-after-free, stack-buffer-overflow 같은 종류와 해당 메모리가 할당·해제된 위치의 스택까지 함께 보여 주며, 실행 속도는 대략 2배 정도 느려져 테스트 빌드에서 상시 켜 두기 좋습니다. Valgrind는 재컴파일 없이 쓸 수 있는 대신 수십 배 느립니다.

Undefined Reference (링커 에러)

에러 메시지:

undefined reference to `myFunction()'

원인:

  • 함수 선언만 있고 정의가 없음
  • 라이브러리 링크 누락
  • 네임스페이스 불일치 예제:
// header.h
void myFunction();
// main.cpp
#include "header.h"
int main() {
    myFunction();  // ❌ undefined reference
    return 0;
}
// 해결: 정의 추가
// impl.cpp
#include "header.h"
void myFunction() {
    // 구현
}
// 컴파일
g++ main.cpp impl.cpp -o main  // ✅

라이브러리 링크 누락:

# 에러
g++ main.cpp -o main
# undefined reference to `pthread_create'
# 해결: -pthread 추가 (컴파일·링크 모두에 적용)
g++ main.cpp -o main -pthread

undefined reference는 컴파일은 성공했고 링크 단계에서 실패했다는 뜻입니다. 컴파일러는 헤더의 선언만 보고 “이런 함수가 어딘가 있겠지”라고 넘어가고, 링커가 모든 오브젝트 파일과 라이브러리를 뒤져도 정의를 찾지 못할 때 이 에러가 납니다. 흔한 원인은 위 예제 외에도 몇 가지가 더 있습니다. 템플릿 함수를 .cpp에만 정의해서 다른 파일에서 인스턴스화할 수 없는 경우, C 라이브러리 헤더를 extern "C" 없이 포함해서 이름 맹글링이 맞지 않는 경우, 그리고 링크 순서 문제입니다. GNU ld는 명령줄 순서대로 라이브러리를 처리하므로 g++ -lfoo main.o처럼 라이브러리를 사용처보다 앞에 두면 정의가 있어도 찾지 못합니다. 참고로 glibc 2.34부터는 pthread 함수가 libc에 통합되어 최신 배포판에서는 이 에러가 잘 나지 않지만, 이식성을 위해 -pthread 옵션은 계속 붙이는 것이 관례입니다. Windows MSVC에서는 같은 문제가 LNK2019: unresolved external symbol로 나타납니다.

Template 에러

에러 메시지:

error: no matching function for call to 'std::vector<int>::push_back(const char [6])'

원인: 템플릿 타입 불일치

에러 메시지의 괄호 안 타입이 std::string이 아니라 const char [6]인 점에 주목하세요. 문자열 리터럴은 std::string이 아니라 널 문자를 포함한 문자 배열이고, 컴파일러는 인자의 실제 타입을 그대로 보여 줍니다. 템플릿 에러는 이 한 줄 뒤에 “candidate: …” 목록이 수십 줄 이어지는데, 대부분 “이 오버로드는 이래서 안 맞았다”는 설명입니다. 첫 번째 error: 줄과 내 파일이 등장하는 required from here 줄만 먼저 읽으면 원인을 빠르게 좁힐 수 있습니다. 예제:

std::vector<int> vec;
vec.push_back("hello");  // ❌ 타입 에러
// 해결
std::vector<std::string> vec;
vec.push_back("hello");  // ✅

Python 흔한 에러

IndentationError

에러 메시지:

IndentationError: expected an indented block

원인: 들여쓰기 불일치 예제:

# ❌ 에러
def greet():
print("Hello")  # 들여쓰기 없음
# ✅ 해결
def greet():
    print("Hello")
# ❌ 탭과 스페이스 혼용
def calculate():
    x = 10  # 스페이스 4칸
	y = 20  # 탭 1개
    return x + y  # TabError: inconsistent use of tabs and spaces in indentation
# ✅ 해결: 스페이스로 통일
def calculate():
    x = 10
    y = 20
    return x + y

예방:

  • 에디터 설정: 스페이스 4칸
  • .editorconfig 사용
# .editorconfig
[*.py]
indent_style = space
indent_size = 4

NameError

에러 메시지:

NameError: name 'x' is not defined

원인: 변수를 정의하기 전에 사용 예제:

# ❌ 에러
print(x)  # x가 정의되지 않음
# ✅ 해결
x = 10
print(x)
# ❌ 함수 내부에서 전역 변수 수정
count = 0
def increment():
    count += 1  # UnboundLocalError
# ✅ 해결: global 키워드
count = 0
def increment():
    global count
    count += 1

두 번째 예제는 이름은 분명히 있는데도 에러가 난다는 점에서 헷갈립니다. Python은 함수를 컴파일할 때 함수 안 어디에서든 대입되는 이름을 지역 변수로 결정합니다. count += 1은 대입이므로 count는 함수 전체에서 지역 변수가 되고, 값을 읽는 시점에는 아직 지역 count에 값이 없어서 UnboundLocalError(NameError의 하위 클래스)가 납니다. Python 3.11 이상에서는 cannot access local variable 'count' where it is not associated with a value, 그 이전에는 local variable 'count' referenced before assignment라는 메시지로 나옵니다. global은 해결책이지만 전역 상태를 늘리므로, 가능하면 값을 인자로 받아 반환하는 형태(count = increment(count))가 테스트하기 쉽습니다. 중첩 함수에서 바깥 함수의 변수를 바꿀 때는 global 대신 nonlocal을 씁니다.

TypeError

에러 메시지:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

원인: 타입 불일치 예제:

# ❌ 에러
result = 10 + "20"  # int + str
# ✅ 해결
result = 10 + int("20")  # 30
result = str(10) + "20"  # "1020"
# ❌ None 타입 연산
def get_value():
    return None
x = get_value()
y = x + 10  # TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
# ✅ 해결: None 체크
x = get_value()
if x is not None:
    y = x + 10

NoneType 에러는 에러가 난 줄보다 값을 만든 곳에 원인이 있는 경우가 대부분입니다. 함수가 어떤 분기에서 return을 빠뜨리면 조용히 None을 돌려주고, list.sort()처럼 제자리에서 수정하는 메서드의 반환값(None)을 변수에 받는 실수도 흔합니다(items = items.sort()). 저는 이 에러를 보면 먼저 그 변수에 값을 대입한 함수가 모든 경로에서 값을 반환하는지부터 확인합니다. 타입 힌트(def get_value() -> int | None:)를 달고 mypy나 Pyright를 돌리면 이런 가능성을 실행 전에 경고받을 수 있습니다.

IndexError

에러 메시지:

IndexError: list index out of range

원인: 리스트 범위 초과 예제:

# ❌ 에러
arr = [1, 2, 3]
print(arr[5])  # IndexError
# ✅ 해결: 범위 체크
if len(arr) > 5:
    print(arr[5])
else:
    print("인덱스 범위 초과")
# ✅ 또는 try-except
try:
    print(arr[5])
except IndexError:
    print("인덱스 범위 초과")

Java 흔한 에러

NullPointerException

에러 메시지:

java.lang.NullPointerException

원인: null 객체의 메서드 호출 예제:

// ❌ 에러
String str = null;
int len = str.length();  // NullPointerException
// ✅ 해결: null 체크
String str = null;
if (str != null) {
    int len = str.length();
}
// ✅ Java 8+ Optional 사용
Optional<String> opt = Optional.ofNullable(str);
int len = opt.map(String::length).orElse(0);
// ✅ Java 16+ 패턴 매칭 (null이면 instanceof가 false)
if (str instanceof String s) {
    int len = s.length();
}

Java 14부터는 NPE 메시지가 훨씬 친절해졌습니다(JEP 358, Helpful NullPointerExceptions). 예전에는 java.lang.NullPointerException 한 줄뿐이었지만, 지금은 Cannot invoke "String.length()" because "str" is null처럼 어떤 식의 어느 부분이 null이었는지 알려 줍니다. user.getAddress().getCity()처럼 한 줄에 호출이 여러 번 이어질 때 특히 유용합니다. 지역 변수 이름이 <local1>로 표시된다면 -g 옵션 없이 컴파일되어 디버그 정보가 없는 경우입니다. Optional은 “값이 없을 수 있음”을 반환 타입으로 표현하는 용도이며, 필드나 매개변수 타입으로 쓰는 것은 권장되지 않습니다.

ClassCastException

에러 메시지:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

원인: 잘못된 타입 캐스팅 예제:

// ❌ 에러
Object obj = "Hello";
Integer num = (Integer) obj;  // ClassCastException
// ✅ 해결: instanceof 체크
Object obj = "Hello";
if (obj instanceof Integer) {
    Integer num = (Integer) obj;
} else {
    System.out.println("Integer가 아닙니다");
}
// ✅ 제네릭 사용 (타입 안전)
List<Integer> list = new ArrayList<>();
list.add(10);
Integer num = list.get(0);  // 캐스팅 불필요

ConcurrentModificationException

에러 메시지:

java.util.ConcurrentModificationException

원인: 순회 중 컬렉션 수정 예제:

// ❌ 에러
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
for (Integer num : list) {
    if (num % 2 == 0) {
        list.remove(num);  // ConcurrentModificationException
    }
}
// ✅ 해결: Iterator 사용
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    Integer num = it.next();
    if (num % 2 == 0) {
        it.remove();
    }
}
// ✅ 또는 removeIf 사용 (Java 8+)
list.removeIf(num -> num % 2 == 0);

이름과 달리 이 예외는 멀티스레드와 무관하게 단일 스레드에서도 납니다. ArrayList는 구조가 바뀔 때마다 modCount를 올리고, for-each가 내부적으로 쓰는 반복자는 next()를 호출할 때마다 이 값이 처음과 같은지 확인합니다(fail-fast). 값이 달라졌으면 “순회 중에 누가 구조를 바꿨다”고 판단해 예외를 던집니다. 흥미로운 점은 끝에서 두 번째 요소를 지우면 예외가 나지 않는다는 것입니다. 삭제 후 크기가 줄어 hasNext()가 false를 돌려주면서 검사 없이 루프가 끝나고, 마지막 요소는 조용히 검사에서 빠집니다. 이 때문에 테스트 데이터에 따라 버그가 보였다 안 보였다 하므로, 순회 중 삭제는 처음부터 removeIf나 Iterator.remove()로 쓰는 것이 안전합니다.


JavaScript 흔한 에러

TypeError: Cannot read properties of undefined

에러 메시지:

TypeError: Cannot read properties of undefined (reading 'name')

(Chrome·Node.js 16 이후의 V8 메시지입니다. 예전 버전은 Cannot read property 'name' of undefined, Firefox는 user is undefined처럼 표시합니다.) 원인: undefined 객체의 속성 접근 예제:

// ❌ 에러
const user = undefined;
console.log(user.name);  // TypeError
// ✅ 해결: Optional Chaining (ES2020)
console.log(user?.name);  // undefined (에러 안 남)
// ✅ 기본값 설정
const name = user?.name ?? 'Guest';
console.log(name);  // 'Guest'
// ✅ 명시적 체크
if (user && user.name) {
    console.log(user.name);
}

Optional chaining은 에러를 없애 주지만 원인을 숨길 수도 있습니다. API 응답에 반드시 있어야 하는 user가 없는 것이라면 ?.로 넘어가는 대신 그 지점에서 명확한 에러를 내는 편이 디버깅이 쉽습니다. 이 에러가 가장 자주 나는 곳은 비동기 데이터가 도착하기 전의 첫 렌더링입니다. React 컴포넌트가 useState()의 초깃값 undefined 상태로 한 번 렌더링된 뒤에 데이터가 들어오므로, 초깃값을 빈 객체·배열로 두거나 로딩 상태를 따로 처리해야 합니다. 또 ??는 null/undefined일 때만 기본값을 쓰고, ||는 0이나 빈 문자열도 기본값으로 바꾼다는 차이가 있습니다.

ReferenceError: x is not defined

에러 메시지:

ReferenceError: x is not defined

원인: 선언되지 않은 변수 사용 예제:

// ❌ 에러
console.log(x);  // ReferenceError
// ✅ 해결
const x = 10;
console.log(x);
// ❌ 호이스팅 문제
console.log(y);  // undefined (var는 호이스팅)
var y = 10;
// ✅ let/const 사용 (TDZ)
console.log(z);  // ReferenceError: Cannot access 'z' before initialization
const z = 10;

같은 ReferenceError라도 메시지가 둘로 나뉩니다. x is not defined는 그 이름이 스코프 체인 어디에도 없다는 뜻이라 오타나 import 누락을 의심하고, Cannot access 'z' before initialization은 선언은 있지만 실행이 아직 그 줄에 도달하지 않았다는 뜻이라 선언 순서나 모듈 간 순환 import를 의심합니다. var의 undefined는 에러가 나지 않아서 오히려 찾기 어려운 버그가 되므로, let/const의 에러가 더 안전한 동작이라고 보는 것이 맞습니다.

Promise 에러

에러 메시지:

UnhandledPromiseRejectionWarning: Error: Failed to fetch

원인: Promise rejection 미처리 예제:

// ❌ 에러 (catch 없음)
fetch('https://api.example.com/data')
  .then(res => res.json())
  .then(data => console.log(data));
// 네트워크 에러 시 UnhandledPromiseRejection
// ✅ 해결: catch 추가
fetch('https://api.example.com/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error('에러:', err));
// ✅ async/await 사용
async function fetchData() {
  try {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error('에러:', err);
  }
}

Node.js 15부터는 처리되지 않은 rejection이 경고가 아니라 프로세스 종료로 바뀌었습니다(--unhandled-rejections=throw가 기본값). 개발 중에는 경고만 보고 넘어가던 코드가 Node 버전을 올린 뒤 운영 서버를 죽이는 사례가 이 변화에서 나왔습니다.

fetch에는 흔히 놓치는 함정이 하나 더 있습니다. fetch는 네트워크 자체가 실패했을 때만 reject하고, 서버가 404나 500을 돌려주면 정상적으로 resolve됩니다. 위 코드는 500 응답의 HTML 에러 페이지를 res.json()으로 파싱하다 SyntaxError: Unexpected token '<'를 내게 됩니다. 이 에러를 보면 JSON 파싱 문제가 아니라 응답 상태 코드부터 확인해야 하며, if (!res.ok) throw new Error('HTTP ' + res.status)를 res.json() 앞에 두는 것이 기본 패턴입니다.

this 바인딩 문제

에러 메시지:

TypeError: Cannot read properties of undefined (reading 'name')

원인: this가 예상과 다른 객체를 가리킴 예제:

// ❌ 에러
class Person {
  constructor(name) {
    this.name = name;
  }
  
  greet() {
    console.log(`Hello, ${this.name}`);
  }
}
const person = new Person('Alice');
setTimeout(person.greet, 1000);  // TypeError: this is undefined
// ✅ 해결 1: 화살표 함수
setTimeout(() => person.greet(), 1000);
// ✅ 해결 2: bind
setTimeout(person.greet.bind(person), 1000);
// ✅ 해결 3: 클래스 필드 (화살표 함수)
class Person {
  constructor(name) {
    this.name = name;
  }
  
  greet = () => {
    console.log(`Hello, ${this.name}`);
  }
}

JavaScript의 this는 함수가 정의된 곳이 아니라 호출되는 방식으로 정해집니다. person.greet()처럼 점 앞에 객체가 있으면 그 객체가 this가 되지만, setTimeout(person.greet, 1000)은 함수만 떼어서 넘기므로 나중에 그냥 greet()로 호출됩니다. 클래스 본문은 자동으로 strict mode라 이때 this는 undefined가 되고, 그래서 this.name에서 에러가 납니다. 이벤트 리스너(button.addEventListener('click', this.handle))와 React 클래스 컴포넌트에서 같은 문제가 자주 생깁니다. 해결 3의 클래스 필드 화살표 함수는 인스턴스마다 함수를 새로 만들기 때문에 프로토타입 메서드보다 메모리를 조금 더 쓰고, 상속한 클래스에서 super.greet()로 호출할 수 없다는 제약이 있습니다.


디버깅 전략

체계적 디버깅 프로세스

flowchart TD
    A[에러 발생] --> B[에러 메시지 읽기]
    B --> C{컴파일 에러?}
    C -->|예| D[문법/타입 체크]
    C -->|아니오| E{런타임 에러?}
    E -->|예| F[스택 트레이스 확인]
    E -->|아니오| G[논리 에러]
    
    D --> H[에러 위치 수정]
    F --> I[디버거로 추적]
    G --> J[로그/테스트 추가]
    
    H --> K[재컴파일]
    I --> L[원인 파악]
    J --> L
    L --> M[수정 및 테스트]

디버깅 도구 비교

언어디버거로깅테스트
C++GDB, LLDB, Visual Studio Debuggerspdlog, glogGoogle Test, Catch2
Pythonpdb, ipdblogging, logurupytest, unittest
JavaIntelliJ Debugger, jdbLog4j, SLF4JJUnit, TestNG
JavaScriptChrome DevTools, VS Codeconsole, winstonJest, Mocha

디버깅 팁

1. 에러 메시지를 정확히 읽기

❌ "에러가 났어요" (너무 모호)
✅ "TypeError: Cannot read property 'name' of undefined at line 42" (구체적)

2. 최소 재현 코드 만들기

// 복잡한 코드에서 에러 발생
// → 최소한의 코드로 재현
// 원본 (100줄)
function complexFunction() {
  // ... 복잡한 로직
  const result = data.map(x => x.value).filter(x => x > 10);
  // ... 더 복잡한 로직
}
// 최소 재현 (5줄)
const data = [{ value: 5 }, { value: 15 }];
const result = data.map(x => x.value).filter(x => x > 10);
console.log(result);  // [15]

3. 이분 탐색으로 원인 찾기

# 코드가 100줄인데 어디서 에러가 나는지 모름
# → 절반씩 주석 처리하며 범위 좁히기
# 1-50줄 주석 → 에러 안 남 → 1-50줄에 원인
# 1-25줄 주석 → 에러 남 → 26-50줄에 원인
# 26-37줄 주석 → 에러 안 남 → 26-37줄에 원인
# ...

코드 대신 이력에 이분 탐색을 적용할 수도 있습니다. “어제까지는 됐는데 오늘 안 된다”면 git bisect start, git bisect bad(현재), git bisect good <정상이던 커밋>을 입력한 뒤 Git이 골라 주는 중간 커밋마다 테스트 결과를 알려 주면, 커밋 1000개 사이에서도 10번 안팎의 확인으로 문제를 만든 커밋을 찾아냅니다. 재현 스크립트가 있다면 git bisect run ./test.sh로 자동화할 수 있습니다. 4. 로그 추가

// C++
#include <iostream>
void process(int x) {
    std::cout << "DEBUG: x = " << x << std::endl;
    // ... 로직
}
# Python
import logging
logging.basicConfig(level=logging.DEBUG)
def process(x):
    logging.debug(f"x = {x}")
    # ... 로직

5. 디버거 사용

# Python pdb
python -m pdb script.py
# Node.js
node --inspect-brk script.js
# Chrome에서 chrome://inspect 열기

정리

핵심 요약

C++ 흔한 에러:

  • Segmentation Fault: 메모리 접근 에러
  • Undefined Reference: 링커 에러
  • Template 에러: 타입 불일치 Python 흔한 에러:
  • IndentationError: 들여쓰기 불일치
  • NameError: 변수 미정의
  • TypeError: 타입 불일치 Java 흔한 에러:
  • NullPointerException: null 접근
  • ClassCastException: 잘못된 캐스팅
  • ConcurrentModificationException: 순회 중 수정 JavaScript 흔한 에러:
  • TypeError: undefined 접근
  • ReferenceError: 변수 미정의
  • Promise rejection: 비동기 에러 미처리

디버깅 체크리스트

  • 에러 메시지 정확히 읽기
  • 스택 트레이스 확인
  • 최소 재현 코드 만들기
  • 로그 추가
  • 디버거 사용
  • 테스트 코드 작성

다음 단계

각 언어의 자세한 디버깅 방법은 아래 글을 참고하세요:

관련 주제:


자주 묻는 질문 (FAQ)

Q. JavaScript에서 UnhandledPromiseRejection 경고가 뜨면 어떻게 고치나요?

A. Promise가 reject되었는데 이를 처리하는 .catch()나 try/catch가 없을 때 나는 경고입니다. async 함수 안에서는 await하는 부분을 try/catch로 감싸고, Promise 체인이라면 끝에 .catch()를 붙여야 합니다. 최근 Node.js는 처리되지 않은 rejection이 발생하면 프로세스를 종료하므로, await 없이 호출만 해 둔 비동기 함수가 없는지도 확인해야 합니다.


같이 보면 좋은 글