원문: https://blog.openreplay.com/an-introduction-to-javascript-error-handling
개발 경험이 쌓일수록 당신의 앱은 더욱 견고해집니다. 코딩 기술의 개선이 많은 부분을 차지하지만, 작은 엣지 케이스를 다루는 방법도 배우게 될 것입니다. 일반적으로 첫 번째 사용자가 당신의 새 시스템에 접속할 때 문제가 발생할 수 있습니다.
어떤 에러들은 피할 수 있습니다.
다른 런타임 에러들은 피하는 것이 불가능합니다.
이 문제들은 일시적일 수 있습니다. 이러한 문제들을 완벽하게 피할 순 없지만, 문제를 예측하고 적절한 조치를 취하여 애플리케이션의 복원력을 높일 수 있습니다.
우리는 모두 애플리케이션에서 에러를 경험했습니다. 그 경험의 일부는 유용합니다.
"해당 파일이 이미 존재합니다. 덮어쓰시겠습니까?"
아래와 같은 에러는 그렇지 않습니다.
"에러 5969"
사용자에게 에러를 표시하는 것은, 다른 모든 옵션을 모두 적용해본 후에 사용할 마지막 수단이어야 합니다.
이미지 로드 실패같이 덜 중요한 에러는 무시할 수 있습니다. 무시하기 어렵다면 에러를 개선하거나 복구하는 동작도 가능합니다. 예를 들어 네트워크 장애로 인해 서버에 데이터를 저장할 수 없다면 indexedDB 또는 로컬스토리지에 임시로 저장하고 몇 분 후에 다시 시도할 수 있습니다. 반복된 저장에 실패하고 유저가 데이터를 잃을 위험이 있는 경우에만 경고를 표시해야 합니다. 그런 경우에도, 유저가 적절한 조치를 취할 수 있는지 확인해야 합니다. 네트워크는 다시 연결할 수 있어도 서버가 다운된 경우라면 도움이 되지 않습니다.
자바스크립트에서 에러 핸들링은 간단하지만 종종 미스테리에 싸여 있고 비동기 코드를 고려할 때 복잡해질 수 있습니다.
”error"는 예외를 발생시키기 위해 던질(throw) 수 있는 객체입니다 — 에러가 제대로 감지가 되지 않고 적절하게 처리되지 않는다면 프로그램이 중단될 수 있습니다. 생성자에게 선택적으로 메시지 전달하여 에러 객체를 만들 수 있습니다:
const e = new Error('An error has occurred');
new
없이 함수처럼 Error
를 사용할 수도 있습니다 — 여전히 위와 동일하게 Error
객체를 반환합니다.
const e = Error('An error has occurred');
파일 이름과 줄 번호를 두 번째와 세 번째 매개변수로 전달할 수 있습니다.
const e = new Error("An error has occurred", "script.js", 99);
위 구문은 현재 파일에서 Error
객체를 만든 행이 기본값이기 때문에 대부분의 경우엔 필요하지 않습니다.
생성된 Error
객체에는 읽고 쓸 수 있는 다음과 같은 속성이 있습니다.
.name
: 에러 유형의 이름 (발생한 "Error"
).message
: 에러 메시지다음 읽기/쓰기 속성은 파이어폭스에서도 지원됩니다.
.fileName
: 에러가 발생한 파일.lineNumber
: 에러가 발생한 줄 번호.columnNumber
: 에러가 발생한 줄의 열 번호.stack
: 스택 추적 — 에러에 도달하기 위해 호출된 함수 목록자바스크립트는 일반적인 에러 뿐 아니라 다음과 같은 특정한 에러 유형들도 지원합니다.
자바스크립트 인터프리터는 필요에 따라 적절한 에러를 발생시킵니다. 대부분의 경우에 사용자 코드에서 Error
또는 TypeError
를 사용할 수 있습니다.
Error
객체를 생성하는 것으로는 아무런 작업도 수행되지 않습니다. 예외를 발생시키려면 Error
를 throw해야 합니다.
throw new Error('An error has occurred');
아래 sum()
함수는 두 인수 중 하나가 숫자가 아닌 경우 TypeError
를 발생시킵니다. return
은 실행되지 않습니다.
function sum(a, b) {
if (isNaN(a) || isNaN(b)) {
throw new TypeError('Value is not a number.');
}
return a + b;
}
Error
객체를 던지는 것이 실용적이지만 다음과 같이 값이나 객체를 사용할 수도 있습니다.
throw 'Error string';
throw 42;
throw true;
throw { message: 'An error', name: 'CustomError' };
예외를 던지면 catch문에서 잡히지 않는 한, 호출 스택이 버블업 됩니다.
잡히지 않은 예외는 결국 스택의 맨 위에 도달해 프로그램이 중지되고, 개발자 도구의 콘솔에 오류가 표시됩니다.
Uncaught TypeError: Value is not a number.
sum https://mysite.com/js/index.js:3
try ... catch 블록에서 블록에서 예외를 잡을 수 있습니다.
try {
console.log( sum(1, 'a') );
}
catch (err) {
console.error( err.message );
}
위 코드는 try {}
블록 코드 부분을 실행하지만, 예외가 발생하면 catch {}
블록이 throw
에서 반환된 객체를 받습니다.
catch
블록은 에러를 분석하고 그에 따라 대응할 수 있습니다.
try {
console.log( sum(1, 'a') );
}
catch (err) {
if (err instanceof TypeError) {
console.error( 'wrong type' );
}
else {
console.error( err.message );
}
}
try 또는 catch 코드가 실행되는지와 관계없이 반드시 실행되야 하는 코드가 있는 경우, 선택적 finally {}
블록을 정의할 수 있습니다. 이것은 정리(cleaning up)할 때 유용할 수 있습니다. 예를 들어 Node.js나 Deno에서 데이터베이스 연결을 닫는 경우가 있습니다:
try {
console.log( sum(1, 'a') );
}
catch (err) {
console.error( err.message );
}
finally {
// 아래 구문은 항상 실행됩니다.
console.log( 'program has ended' );
}
try
블록에는 catch
블록, finally
블록 또는 둘 다 필요합니다.
finally
블록에 반환값이 포함된 경우 해당 값은 try
및 catch
블록의 반환 문에 관계없이 전체 try ... catch ... finally
에 대한 반환 값이 됩니다.
OpenReplay는 사용자가 웹 앱에서 수행하는 작업을 볼 수 있는 오픈 소스 세션 재생 모음으로, 문제를 더 빨리 해결할 수 있도록 도와줍니다. OpenReplay는 데이터를 완전히 제어하기 위해 자체 호스팅됩니다.
디버깅 경험을 즐기세요 - 무료로 OpenReplay를 사용하세요.
try ... catch
블록 및 에러 다시 던지기예외는 스택에 버블업되고 가장 가까운 catch
블록에 의해 단 한 번만 포착됩니다.
try {
try {
console.log( sum(1, 'a') );
}
catch (err) {
console.error('This error will trigger', err.message);
}
}
catch (err) {
console.error('This error will not trigger', err.message);
}
모든 catch
블록은 외부 catch
에 의해 잡히는 새로운 throw
할 수 있습니다. 새로운 에러를 생성할 때 생성자의 cause
속성으로 첫번째 Error
객체를 전달함으로서, 첫번째 Error
를 새로운 Error
로 전달 할 수 있습니다. 이를 통해 에러를 연속적으로 발생시키고 검사할 수 있습니다.
이 예에서는 첫 번째 에러가 두 번째 에러를 발생시키기 때문에 두 개의 catch
블록이 모두 실행됩니다.
try {
try {
console.log( sum(1, 'a') );
}
catch (err) {
console.error('First error caught', err.message);
throw new Error('Second error', { cause: err });
}
}
catch (err) {
console.error('Second error caught', err.message);
console.error('Error cause:', err.cause.message);
}
try ... catch
블록이 실행된 후에 예외를 발생시키기 때문에, 비동기 함수에서 발생시킨(throw) 예외는 잡을 수가 없습니다. 아래 로직은 실패할 것입니다.
function wait(delay = 1000) {
setTimeout(() => {
throw new Error('I am never caught!');
}, delay);
}
try {
wait();
} catch (err) {
// 아래 구문은 절대 실행되지 않습니다.
console.error('caught!', err.message);
}
1초가 지나면 콘솔에 다음과 같이 표시됩니다:
Uncaught Error: I am never caught!
wait http://localhost:8888/:14
콜백을 사용하는 경우, Node.js와 같은 프레임워크 및 런타임에서 가정하는 규칙은 에러를 해당 함수의 첫 번째 매개변수로 반환하는 것입니다. 필요한 경우 수동으로 이 작업을 수행할 수 있지만 예외가 발생하지는 않습니다.
function wait(delay = 1000, callback) {
setTimeout(() => {
callback('I am caught!');
}, delay);
}
wait(1000, (err) => {
if (err) {
throw new Error(err);
}
});
최신 ES6에서는 비동기 함수를 정의할 때 Promise를 반환하는 것이 더 나은 경우가 많습니다. 에러가 발생하면 Promise의 reject 함수는 새로운 Error
객체를 반환할 수 있습니다 (어떤 값이나 객체도 가능하지만):
function wait(delay = 1000) {
return new Promise((resolve, reject) => {
if (isNaN(delay) || delay < 0) {
reject(new TypeError('Invalid delay'));
}
else {
setTimeout(() => {
resolve(`waited ${ delay } ms`);
}, delay);
}
})
}
Promise.catch() 함수는 잘못된 delay
매개변수를 전달할 때 실행되므로 반환된 Error
객체에 대응할 수 있습니다.
// 아래 구문은 실패하므로 catch 블록이 실행됩니다.
wait('x')
.then( res => console.log( res ))
.catch( err => console.error( err.message ))
.finally(() => console.log('done'));
Promise
를 반환하는 모든 함수는 await
문을 사용하여 async
함수에서 호출할 수 있습니다. 위의 .then/.catch
Promise 예제와 동일하게 실행되지만 조금 더 쉽게 읽으려면 try ... catch
블록에 이를 포함할 수 있습니다.
// 즉시 호출되는 (비동기) 함수 표현식
(async () => {
try {
console.log( await wait('x') );
}
catch (err) {
console.error( err.message );
}
finally {
console.log('done');
}
})();
자바스크립트에서는 에러를 만들고 예외를 발생시키는 것이 쉽습니다. 하지만 에러에 적절하게 대응하고 복구가 빠른 애플리케이션을 구축하는 것은 다소 어렵습니다!
드릴 수 있는 최선의 조언은 예상할 수 없는 일을 예상하고 가능한 한 빨리 에러를 처리하라는 것입니다.
async function asyncErrorHandle (promise) {
try { return [await promise, null ] }
catch(e) { return [null, e || 'error'] }
}
const [ result, error ] = await asyncErrorHandle(asyncFn());
if(error) // do your magic
I see the best blissful on your blog and http://monikaroy.in I very much love figuring out them.
좋은 글 감사합니다