[Javascript] 에러 처리 방법
아래 글에 대한 번역 및 정리 글입니다.
code 는 링크 참조하세요.
(솔직히 원문이 왜 이렇게 많은 관심을 받는 글인지 모르겠습니다. )
I. JavaScript Errors and generic handling
throw new Error(‘something went wrong’)
위와 같은 코드는 에러 인스턴스를 생성하고, 에러 처리를 하지 않으면 스크립트 실행을 멈춥니다. 자바스크립트 개발자로 커리어를 시작할 때, 당신은 아마도 직접 이를 처리하지 않을 것입니다. 대신에 다른 라이브러리를 통해 보게 될 것입니다.
e.g. `ReferenceError: fs is not defined`
The Error Object
Error object 에는 우리가 사용할 두가지 properties 가 들어있습니다. 그 중 하나는 message 입니다. 이것은 우리가 argument 로 Error 에 전달한 값 입니다.
아래와 같이 작성 된 코드에서 ‘please improve your code’ 에 접근 하려면, message 값을 보면 됩니다.
const myError = new Error(‘please improve your code’)console.log(myError.message) // please improve your code
두번째는 Error stack trace 인데, 아주 중요한 요소 입니다. stack property 를 통해 접근 할 수 있습니다. error stack 은 에러를 일으킨 파일에 대한 history 를 전달해 줍니다. 또한 stack 은 메세지를 갖고 있으며, 이는 실제 stack 에서 문제가 되는 부분으로 유도하는 역할을 합니다.
Error: please improve your code
at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
Throwing and Handling Errors
이제 에러 인스턴스는 홀로 무엇인가를 발생시키지 않을 겁니다.
e.g. new Error(‘…’)
← 어느 것도 하지 않을 것입니다.
에러가 throw 와 함께 있을 때는 더 흥미로워집니다. 그땐, 전에 말한 바와 같이(원글 참조) 당신이 무엇인가를 처리하는 코드를 넣지 않는 한, 스크립트 코드는 실행을 멈출 것입니다. 잊지마세요. 당신이 Error 를 직접 throw 했다거나, 라이브러리에 의해 throw 되었거나, 런타임 시정에 스스로 생성 되었든(node or the browser)이는 중요하지 않습니다. 몇가지 시나리오에서 이러한 에러를 어떻게 다루는지 함께 보도록 합니다.
try …. catch
아주 간단한 예제입니다만, 종종 에러를 다루는 방법을 잊곤 합니다. async/await 때문에이는 요즘 다시 많이 사용되는 코드입니다.
이는 여러 종류의 에러를 잡을 때 사용되곤 합니다.
const a = 5try {
console.log(b) // b is not defined, so throws an error
} catch (err) {
console.error(err) // will log the error with the error stack
}console.log(a) // still gets executed
try, catch 블록으로 console.log(b) 를 감싸지 않으면, 스크립트 실행은 멈추게 됩니다.
… finally
때로는 에러 유무와 상관 없이 실행시키고 싶은 코드가 있을 것입니다. 그럴 때는 finally
를 사용하면 됩니다. try…catch 뒤에 코드가 있는 듯하지만, 이게 아주 유용할 때가 있습니다.
Enter asynchronity — Callbacks (비동기 콜백 함수)
비동기, 자바스크립트로 개발하시는 분이라면 항상 고려하는 부분입니다.
비동기 함수가 있을 때, 그 함수안에서 에러가 발생한다해도 스크립트 코드는 계속 실행되고 있을 것입니다. 그래서 에러가 즉각적으로 나오지 않을 것입니다. 콜백함수로 에러를 핸들링 할 때( 추천하지 않아요 ), 아래 보는 바와 같이 콜백 함수로 2가지 parameters를 받을 것입니다.
에러가 발생하면, err
parameter 는 Error 와 동일해집니다. 만약 그렇지 않다면, parameter 는 `undefined` or `null`가 될 것입니다. if(err)
에서 return 이 될지, else
에서 다른 작업을 처리할지는 중요합니다. 이에 따라 Error 가 추가로 발생할 수 있습니다. result
가 undefined 이거나, 또는 result.data
값을 쓰려는 상황 등에서 발생할 수 있습니다.
Asynchronity — Promises
비동기로 좀 더 나은 처리를 하는 방법은 promise를 사용하는 것입니다. 여기에 개선된 에러 핸들링과 함께 코드를 좀 더 남기겠습니다. catch
블럭을 사용하는 한, 우리는 더이상 정확한 Error 를 잡아내는데 집중할 필요가 없습니다. promise chaining 을 하게 되면, catch
블럭은 promise 실행과 마지막 catch 블럭 덕분에 모든 에러를 잡아낼 수 있게 됩니다. catch
블럭이 없는 promise 는 스크립트를 종료시키지 않을 것이지만, 가독성 떨어지는 메세지를 던져줄 것입니다.
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
그러므로 항상 catch
블럭을 promise 에 넣어주세요. 아래를 보시죠.
try … catch — again
자바스크립트에서의 async/await 소개하면서 try…catch…finally를 이용해서 에러를 핸들링 하는 원래의 방법으로 돌아왔습니다.
동기 오류를 처리하는 일반적인 방법과 비슷하기에, 만약 원한다면 더 넓은 catch 구문을 사용하는 것이 처리하는데 더 쉽습니다.
II. Generating and handling Errors in the Server
이제 에러를 처리할 수 있는 툴이 생겼으므로, 실제 환경에서 이 툴을 가지고 무엇을 할수 있는지 봅니다. 백엔드에서 에러를 생성하고 그것들을 정상적으로 처리하는 것은 앱에서 중요한 부분입니다. 에러를 어떻게 다루느냐에 따라서 다른 몇가지 접근법들이 있습니다. 커스텀 에러 생성자와 프론트엔드( 또는 다른 api 사용자)로 쉽게 전달 할 수 있는 코드들을 가지고 접근하는 방법을 보여드리겠습니다. 백엔드를 어떻게 상세히 설계하는지는 중요하지 않습니다.
Express.js를 framework 로 사용할 것입니다. 가장 효율적인 에러 처리를 할 수 있는 구조에 대해서 생각해 봅시다.
1.포괄적인 에러 처리, 몇가지 종류의 fallback, 이런 것들은 보통 이렇게 얘기합니다. ‘ 무엇인가 잘못 되었습니다. 다시 시도 하시거나 연락주세요.’ 이것은 좋은 방법이 아닙니다. 그러나 유저에게 무한 로딩 대신에 최소한의 알림은 전달 합니다.
2. 무엇이 문제인지, 어떻게 고칠 수 있는지에 대해서 구체적인 정보를 유저에게 전달하기 위해 특정한 에러 처리 방법
Building a custom Error constructor
기본적인 Error 생성자를 사용할 것이고, 이것을 확장 할 것입니다. 자바스크립트에서의 상속은 위험을 띄고 있습니다만, 이런 상황에서는 아주 유용할 것입니다. 왜 그런걸까요? 우리는 디버깅을 위해서 stack trace 가 필요합니다. native 자바스크립트 에러 생성자를 확장하면 이를 그냥 가져다 쓸 수 있습니다. 우리가 유일하게 해야 할 것은 나중에 err.code 로 접근할 수 있는 code와 프론트엔드에 전달할 http status 를 작성하는 것입니다.
How to handle routing
커스텀 에러 사용준비와 함께 라우팅 구조를 셋팅해야 합니다.
얘기했듯이, 모든 라우트에서 동일하게 에러 핸들링을 할 수 있는 단일 포인트가 필요합니다. 일반적으로 express 는 라우트들이 캡슐화 되어있어 지원하지 않습니다.
이러한 이슈를 처리하기 위해, 라우트 핸들러와 기본적인 기능을 처리할 실제 로직을 구현할 수 있습니다. 이 때, 라우트 함수는 error 를 던질 것입니다. 그리고 프론트엔드로 전달되도록 라우트 함수에게 return 할 것입니다. 백엔드에서 에러가 날 때마다 우리는 프론트엔드에 response 가 전달 되기를 원합니다. (아래 json api 포함해서)
{
error: 'SOME_ERROR_CODE',
description: 'Something bad happened. Please try again or contact support.'
}
압도할 수 있도록 준비하세요. 제 학생은 제가 말 할때 저(글쓴이)에게 화를 냅니다.
처음부터 모든 것을 알지 못해도 괜찮아요. 우선 사용하고 그 다음에 왜 되는 것인지 찾아보세요.
이를 top-down 학습법이라 부르는데, 내가 상당히 좋아한다.(글쓴이)
아래가 당신이 스스로 확인해야 하는 라우트 핸들러이다.
코드속에 주석을 읽을 수 있기를 바랍니다. 그게 아마 여기에 구구절절 설명하는 것보다 훨씬 나을 것입니다. 이제 실제 라우트 파일이 어떻게 보이는지 확인하겠습니다.
이 예제에서 실제 request 에 아무것도 하지 않았습니다. 다른 에러 시나리오를 가짜로 만들고 있습니다. 예를 들면, GET /city
는 3번째 줄에서, POST /city 는 8째줄에서 끝납니다. 이것 또한 쿼리 params 와 함께 작동합니다. 예를 들면 GET /city?startsWith=R
. 프론트엔드가 전달 받을 에러를 처리하지 않는 다거나, 커스텀 error 를 수동으로 전달하지 않을 것입니다.
{
error: 'GENERIC',
description: 'Something went wrong. Please try again or contact support.'
}throw new CustomError('MY_CODE', 400, 'Error description')
위는 아래와 같이 바뀔것입니다.
{
error: 'MY_CODE',
description: 'Error description'
}
이제 우리는 프론트엔드에 부족한 에러 로그를 전달하는게 아닌, 언제나 무엇이 문제인지 유용한 정보를 전달하는 훌륭한 백엔드를 셋업하게 되었습니다.
전체 코드를 보고 싶다면 아래 링크로 가세요. 언제나 수정해서 사용하셔도 괜찮습니다.
III. Displaying Errors to the User
이제 마지막은 프론트엔드에서 error 를 처리하는 것입니다. 첫 과정에서 만들었던 툴과 함께 프론트엔드에서 만들어낸 에러를 처리할 것입니다. 그리고 백엔드로 부터 온 에러는 노출 되어야 합니다. 먼저 어떻게 에러를 노출할지 봅시다. 우리는 react 를 사용할 것입니다.
Saving Errors in React state
다른 데이터처럼 Error와 Error 메세지는 바뀔 수 있습니다. 그러므로 컴포넌트의 state 에 넣어둬야 합니다. 기본적으로 마운팅이 되면, error 를 리셋하려 할 것입니다. 그래야 유저가 처음 페이지를 볼 때, 에러가 보이지 않을 것입니다.
다음으로 우리가 명확히 해야 할 것은 각기 다른 타입의 에러들을 화면의 표현과 매칭 시키는 것입니다. 백엔드에는 3가지 타입이 있습니다.
- 글로벌 에러, e.g. 백엔드로부터 오는 포괄적인 에러들 중 하나, 유저가 sign in 을 안했다거나..
- 백엔드로 부터 오는 특정 에러, e.g. 유저가 sign-in 정보를 백엔드에 넘겼는데, 백엔드가 패스워드가 틀렸다고 응답. 이는 프론트가 확인할 수 없으니, 백엔드로 부터 확인 되야함.
- 프론트엔드 스스로가 생성하는 에러, e.g. email 형식 틀림.
2, 3 은 비슷하기도 하고, 같은 state로 처리할 수도 있습니다. 그러나 그 원인은 다릅니다. 다음에서 코드를 통해서 처리해 나가겠습니다.
React의 native state 를 이용할 것입니다. Mobx 나 Redux 를 사용해도 무방합니다.
Global Errors
보통 이러한 Error 는 가장 밖에 있는 stateful 컴포넌트에 저장합니다. 그리고 static UI Element 를 이용해서 렌더합니다. 아래 상단의 빨간 배너 처럼 디자인 구현은 당신 몫입니다.
코드를 확인해 보겠습니다.
보시다시피, Application.js 안에 Error 가 state 에 담겨 있습니다. 또한 error 를 리셋하거나 바꿀 수 있는 methods 도 가지고 있습니다. 우리는 error 와 리셋 method 를 GlobalError 컴포넌트로 넘겨줄 것입니다. GlobalError 컴포넌트는 메세지를 노출 시킬 것이고, x 버튼을 누르면 error 값을 리셋 시킬 것입니다. 다음 코드를 통해 GlobalError 컴포넌트를 확인해봅시다.
5번째 줄을 보면, Error 가 없다면 아무런 값을 노출시키지 않을 것입니다. 이는 빈 빨간 박스가 화면에 항상 떠 있는 것을 막아줍니다. 예제를 수정해서 x 를 누르면 몇초 뒤에 error 값이 초기화 되도록 할 수 있습니다.
이제 Applications.js 로부터 _setError 를 건내 줌으로써, 당신이 원하는 아무 때나 global error state 를 사용할 수 있습니다. 그리고 global Error 를 설정할 수 있습니다. 예를 들면, 백엔드로 부터 error: ‘GENERIC’ 으로 응답이 올 경우 입니다.
만약 못마땅하다면 여기서 멈추세요. 이미 에러가 발생된다면, global error state 를 수정하고, 페이지 상단에 에러 박스를 띄울 수 있습니다. 계속해서 특정 에러를 어떻게 처리하고 노출할지에 대해서 이어질 것입니다. 왜냐구요? 먼저, 이 글은 에러를 어떻게 다루는지에 대한 글이기 때문입니다. 그래서 여기서 멈출 수가 없습니다. 그리고 UX 유저들은 모든 에러를 globally 하게 노출할지에 대해서 꽤 고민을 많이 할것입니다. … (번역 맞아?)
Handling specific request Errors
global error 와 비슷하게 다른 컴포넌트 안에서 local error state 를 가질 수 있습니다. 다음과 같습니다.
한가지 기억할 것은 error 들은 대게 다른 작업을 유발 시킨다는 것입니다. 에러를 삭제하기 위해서 ‘x’ 를 만드는 것은 상황에 맞지 않을 수 있습니다. 새로운 요청이 있을 때, error 를 지우는 것이 더 맞을 수도 있습니다. 또는 유저가 새로운 변화를 만들어 낼 때, error 를 삭제 할 수 있습니다. e.g. 입력 정보가 바뀔 때.
Frontend origin errors
앞에서 언급했듯이, 이러한 에러들은 백엔드로부터 전달된 특정 에러들 처럼 같은 방법으로 처리 될 수 있습니다. 이번에는 input 이 한개인 예제를 이용해 봅시다. 그리고 유저는 도시를 지울 수 있습니다. 단, 입력이 허용된 도시를 입력했을 때만 입니다.
Error internationalisation using the Error code
아마도 error codes 를 사용하는 것에 대해서 궁금해 할 수 있습니다. e.g. GENERIC
우리는 단순히 백엔드로부터 전달된 에러 메세지를 노출 하였습니다. 당신의 앱이 확대 된다면, 새로운 국가로 확장하기를 희망 할 것이고, 다양한 언어들을 지원하게 되면서 몇가지 문제를 직면하게 될것입니다. 그 시점에 error code를 사용함으로써 유저의 언어에 맞게 올바른 설명을 노출 할 수 있습니다.
I hope you gained some insight as to how to deal with Errors. Quickly typed and just as quickly forgotten console.error(err)
should be a thing of the past now. It is essential to use that for debugging, but it should not end up in your production build. To prevent that, I would recommend you use a logging library, I have been using loglevel in the past and I am pretty happy with it.