JavaScript 오류 처리 및 스 택 추적 상세 설명

때때로 우 리 는 오류 처리 와 스 택 추적 에 관 한 세부 사항 을 무시 합 니 다.그러나 이러한 세부 사항 은 테스트 나 오류 처리 와 관련 된 라 이브 러 리 를 쓰 는 데 매우 유용 합 니 다.예 를 들 어 이번 주 에 Chai 에 아주 좋 은 PR 이 있 습 니 다.이 PR 은 우리 가 스 택 을 처리 하 는 방식 을 크게 개선 하 였 습 니 다.사용자 의 단언 이 실 패 했 을 때,저 희 는 더 많은 힌트 정 보 를 드 리 겠 습 니 다.
스 택 정 보 를 합 리 적 으로 처리 하면 쓸모없는 데 이 터 를 제거 하고 유용 한 데이터 에 만 집중 할 수 있 습 니 다.또한 Errors 대상 과 관련 된 속성 을 잘 이해 한 후에 Errors 를 충분히 이용 하 는 데 도움 이 됩 니 다.
호출 스 택 은 어떻게 작 동 합 니까?
오 류 를 이야기 하기 전에 스 택 을 호출 하 는 원 리 를 알 아야 합 니 다.
함수 가 호출 되 었 을 때,그것 은 창고 의 맨 위 에 눌 려 있 고,이 함수 가 실 행 된 후에 창고 의 맨 위 에서 제 거 됩 니 다.
스 택 의 데이터 구 조 는 후진 선 출 로 LIFO(last in,first out)로 유명 합 니 다.
예 를 들 면:

function c() {
  console.log('c');
}
 
function b() {
  console.log('b');
  c();
}
 
function a() {
  console.log('a');
  b();
}
 
a();
상기 예제 에서 함수 a 가 실 행 될 때 스 택 의 상단 에 추 가 됩 니 다.그리고 함수 b 가 함수 a 의 내부 에서 호출 될 때 함수 b 는 스 택 의 상단 에 눌 립 니 다.함수 c 가 함수 b 의 내부 에서 호출 될 때 도 스 택 의 상단 에 눌 립 니 다.
함수 c 가 실 행 될 때 스 택 에는 a,b,c 가 포함 되 어 있 습 니 다(이 순서 로).
함수 c 가 실 행 된 후에 스 택 의 상단 에서 제거 되 고 함수 가 호출 한 제어 흐름 은 함수 b 로 돌아 갑 니 다.함수 b 가 실 행 된 후에 도 스 택 의 상단 에서 제거 되 고 함수 가 호출 한 제어 흐름 은 함수 a 로 돌아 갑 니 다.마지막 으로 함수 a 가 실 행 된 후에 도 스 택 의 상단 에서 제 거 됩 니 다.
demo 에서 스 택 을 더 잘 보 여주 기 위해 서 는 console.trace()를 사용 하여 콘 솔 에서 현재 스 택 데 이 터 를 출력 할 수 있 습 니 다.또한 출력 된 스 택 데 이 터 를 위 에서 아래로 읽 어야 합 니 다.

function c() {
  console.log('c');
  console.trace();
}
 
function b() {
  console.log('b');
  c();
}
 
function a() {
  console.log('a');
  b();
}
 
a();
Node 의 REPL 모드 에서 상기 코드 를 실행 하면 다음 과 같은 출력 을 얻 을 수 있 습 니 다.

Trace
  at c (repl:3:9)
  at b (repl:3:1)
  at a (repl:3:1)
  at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node's internals
  at realRunInThisContextScript (vm.js:22:35)
  at sigintHandlersWrap (vm.js:98:12)
  at ContextifyScript.Script.runInThisContext (vm.js:24:12)
  at REPLServer.defaultEval (repl.js:313:29)
  at bound (domain.js:280:14)
  at REPLServer.runBound [as eval] (domain.js:293:12)
보시 다시 피 함수 c 에서 출력 할 때 스 택 에는 함수 a,b,c 가 포함 되 어 있 습 니 다.
함수 c 가 실 행 된 후에 함수 b 에서 현재 스 택 데 이 터 를 출력 하면 함수 c 가 스 택 의 상단 에서 제거 되 었 음 을 볼 수 있 습 니 다.이때 스 택 에는 함수 a 와 b 만 포 함 됩 니 다.

function c() {
  console.log('c');
}
 
function b() {
  console.log('b');
  c();
  console.trace();
}
 
function a() {
  console.log('a');
  b();
}
보시 다시 피 함수 c 가 실 행 된 후에 스 택 의 상단 에서 제거 되 었 습 니 다.

Trace
  at b (repl:4:9)
  at a (repl:3:1)
  at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node's internals
  at realRunInThisContextScript (vm.js:22:35)
  at sigintHandlersWrap (vm.js:98:12)
  at ContextifyScript.Script.runInThisContext (vm.js:24:12)
  at REPLServer.defaultEval (repl.js:313:29)
  at bound (domain.js:280:14)
  at REPLServer.runBound [as eval] (domain.js:293:12)
  at REPLServer.onLine (repl.js:513:10)
Error 대상 과 오류 처리
프로그램 이 실 행 될 때 오류 대상 을 던 집 니 다.오류 대상 은 사용자 정의 오류 대상 으로 계승 할 수 있 습 니 다.
Error.prototype 대상 은 다음 과 같은 속성 을 포함 합 니 다.
constructor C 가 인 스 턴 스 를 가리 키 는 구조 함수
message C 오류 정보
namec 잘못된 이름(형식)
위 는 Error.prototype 의 표준 속성 입 니 다.그 밖 에 서로 다른 운영 환경 에 특정한 속성 이 있 습 니 다.예 를 들 어 Node,Firefox,Chrome,Edge,IE 10+,Opera 와 Safari 6+와 같은 환경 에서 Error 대상 은 stack 속성 을 가지 고 있 습 니 다.이 속성 은 잘못된 스 택 궤적 을 포함 합 니 다.잘못된 인 스 턴 스 의 스 택 궤적 은 구조 함수 이후 의 모든 스 택 구 조 를 포함 합 니 다.
Error 대상 의 특정 속성 에 대해 더 알 고 싶다 면 MDN 의 이 글 을 읽 을 수 있 습 니 다.
오 류 를 던 지기 위해 서 는 throw 키 워드 를 사용 해 야 합 니 다.catch 를 위해 서 는 try..catch 를 사용 해 야 합 니 다.오류 가 발생 할 수 있 는 코드 를 포함 하고 있 습 니 다.Catch 의 인 자 는 오류 인 스 턴 스 입 니 다.
자바 와 마찬가지 로 자바 스 크 립 트 도 try/catch 이후 finally 키 워드 를 사용 할 수 있 습 니 다.오 류 를 처리 한 후 finally 구문 블록 에서 제거 작업 을 할 수 있 습 니 다.
문법 적 으로 try 구문 블록 을 사용 할 수 있 습 니 다.그 다음 에 catch 구문 블록 을 따 르 지 않 아 도 되 지만 finally 구문 블록 을 따라 야 합 니 다.이것 은 세 가지 서로 다른 try 구문 형식 이 있다 는 것 을 의미 합 니 다.
try…catch
try…finally
try…catch…finally
Try 문 구 는 try 문 구 를 삽입 할 수 있 습 니 다:

try {
  try {
    throw new Error('Nested error.'); // The error thrown here will be caught by its own `catch` clause
  } catch (nestedErr) {
    console.log('Nested catch'); // This runs
  }
} catch (err) {
  console.log('This will not run.');
}
catch 나 finally 에 try 문 구 를 삽입 할 수도 있 습 니 다:

try {
  throw new Error('First error');
} catch (err) {
  console.log('First catch running');
  try {
    throw new Error('Second error');
  } catch (nestedErr) {
    console.log('Second catch running.');
  }
}
try {
  console.log('The try block is running...');
} finally {
  try {
    throw new Error('Error inside finally.');
  } catch (err) {
    console.log('Caught an error inside the finally block.');
  }
}
중요 한 것 은 오 류 를 던 질 때 Error 대상 이 아 닌 간단 한 값 만 던 질 수 있다 는 것 입 니 다.멋 지고 허용 되 는 것 처럼 보이 지만 추천 하 는 방법 은 아 닙 니 다.특히 타인 의 코드 를 처리 해 야 하 는 라 이브 러 리 와 프레임 워 크 개발 자 에 게 는 참고 할 기준 이 없 기 때 문 입 니 다.사용자 에 게 서 무엇 을 얻 을 수 있 는 지 알 수 없습니다.사용자 가 Error 대상 을 던 질 것 이 라 고 믿 을 수 없습니다.그렇게 하지 않 고 문자열 이나 수 치 를 간단하게 던 질 수 있 기 때 문 입 니 다.스 택 정보 와 다른 메타 정 보 를 처리 하기 어렵 다 는 뜻 입 니 다.
예 를 들 면:

function runWithoutThrowing(func) {
  try {
    func();
  } catch (e) {
    console.log('There was an error, but I will not throw it.');
    console.log('The error\'s message was: ' + e.message)
  }
}
 
function funcThatThrowsError() {
  throw new TypeError('I am a TypeError.');
}
 
runWithoutThrowing(funcThatThrowsError);
사용자 가 함수 runWithout Throwing 에 전달 하 는 인자 가 잘못된 대상 을 던 지면 위의 코드 는 오 류 를 정상적으로 포착 할 수 있 습 니 다.그리고 문자열 을 던 지면 문제 가 발생 할 수 있 습 니 다.

function runWithoutThrowing(func) {
  try {
    func();
  } catch (e) {
    console.log('There was an error, but I will not throw it.');
    console.log('The error\'s message was: ' + e.message)
  }
}
 
function funcThatThrowsString() {
  throw 'I am a String.';
}
 
runWithoutThrowing(funcThatThrowsString);
현재 두 번 째 console.log 는 undefined 를 출력 합 니 다.이것 은 중요 하지 않 지만 Error 대상 이 특정한 속성 을 가지 고 있 는 지 확인 하거나 다른 방식 으로 Error 대상 의 특정한 속성(예 를 들 어 Chai 의 throws 단언 방법)을 처리 하려 면 프로그램의 정확 한 운행 을 확보 하기 위해 많은 작업 을 해 야 합 니 다.
또한 Error 대상 이 아니라면 stack 속성 을 가 져 올 수 없습니다.
Errors 도 다른 대상 이 될 수 있 습 니 다.당신 도 그것들 을 던 질 필요 가 없습니다.이것 도 대부분의 반전 함수 가 Errors 를 첫 번 째 매개 변수 로 하 는 이유 입 니 다.예 를 들 어:

const fs = require('fs');
 
fs.readdir('/example/i-do-not-exist', function callback(err, dirs) {
  if (err instanceof Error) {
    // `readdir` will throw an error because that directory does not exist
    // We will now be able to use the error object passed by it in our callback function
    console.log('Error Message: ' + err.message);
    console.log('See? We can use Errors without using try statements.');
  } else {
    console.log(dirs);
  }
});
마지막 으로 Error 대상 도 rejected promise 에 사용 할 수 있 습 니 다.이 는 rejected promise 를 쉽게 처리 할 수 있 습 니 다.

new Promise(function(resolve, reject) {
  reject(new Error('The promise was rejected.'));
}).then(function() {
  console.log('I am an error.');
}).catch(function(err) {
  if (err instanceof Error) {
    console.log('The promise was rejected with an error.');
    console.log('Error Message: ' + err.message);
  }
});
처리 창고
이 절 은 Error.capture StackTrace 를 지원 하 는 운영 환경 입 니 다.예 를 들 어 Nodejs.
Error.captureStack Trace 의 첫 번 째 매개 변 수 는 object 이 고 두 번 째 선택 가능 한 매개 변 수 는 function 입 니 다.Error.captureStack Trace 는 스 택 정 보 를 캡 처 하고 첫 번 째 매개 변수 에 stack 속성 을 만들어 서 캡 처 된 스 택 정 보 를 저장 합 니 다.두 번 째 매개 변 수 를 제공 하면 이 함 수 는 스 택 호출 의 종점 으로 사 용 됩 니 다.따라서,캡 처 된 스 택 정 보 는 이 함수 가 호출 되 기 전의 정보 만 표시 합 니 다.
다음 데모 두 개 로 설명 하 겠 습 니 다.첫 번 째 는 포 획 된 스 택 정 보 를 일반 대상 에 만 저장 합 니 다.

const myObj = {};
 
function c() {
}
 
function b() {
  // Here we will store the current stack trace into myObj
  Error.captureStackTrace(myObj);
  c();
}
 
function a() {
  b();
}
 
// First we will call these functions
a();
 
// Now let's see what is the stack trace stored into myObj.stack
console.log(myObj.stack);
 
// This will print the following stack to the console:
//  at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//  at a (repl:2:1)
//  at repl:1:1 <-- Node internals below this line
//  at realRunInThisContextScript (vm.js:22:35)
//  at sigintHandlersWrap (vm.js:98:12)
//  at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//  at REPLServer.defaultEval (repl.js:313:29)
//  at bound (domain.js:280:14)
//  at REPLServer.runBound [as eval] (domain.js:293:12)
//  at REPLServer.onLine (repl.js:513:10)
위의 예 시 를 통 해 알 수 있 듯 이 먼저 함수 a(스 택 에 눌 려 있 음)를 호출 한 다음 에 a 에서 함수 b(스 택 에 눌 려 있 고 a 위 에 있 음)를 호출 한 다음 에 b 에서 현재 스 택 정 보 를 캡 처 하여 my Obj 에 저장 합 니 다.따라서 콘 솔 에서 출력 한 스 택 메시지 에는 a 와 b 의 호출 정보 만 포함 되 어 있 습 니 다.
현재,우 리 는 Error.capture StackTrace 에 두 번 째 매개 변수 로 함 수 를 전달 하고 출력 정 보 를 봅 니 다.

const myObj = {};
 
function d() {
  // Here we will store the current stack trace into myObj
  // This time we will hide all the frames after `b` and `b` itself
  Error.captureStackTrace(myObj, b);
}
 
function c() {
  d();
}
 
function b() {
  c();
}
 
function a() {
  b();
}
 
// First we will call these functions
a();
 
// Now let's see what is the stack trace stored into myObj.stack
console.log(myObj.stack);
 
// This will print the following stack to the console:
//  at a (repl:2:1) <-- As you can see here we only get frames before `b` was called
//  at repl:1:1 <-- Node internals below this line
//  at realRunInThisContextScript (vm.js:22:35)
//  at sigintHandlersWrap (vm.js:98:12)
//  at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//  at REPLServer.defaultEval (repl.js:313:29)
//  at bound (domain.js:280:14)
//  at REPLServer.runBound [as eval] (domain.js:293:12)
//  at REPLServer.onLine (repl.js:513:10)
//  at emitOne (events.js:101:20)
함수 b 를 두 번 째 매개 변수 로 Error.capture StackTrace Function 에 전달 할 때 출력 된 스 택 은 함수 b 호출 전의 정보 만 포함 합 니 다(Error.capture StackTrace Function 은 함수 d 에서 호출 되 었 음 에 도 불구 하고).이것 은 바로 콘 솔 에서 만 a 를 출력 한 이유 입 니 다.이러한 처리 방식 의 장점 은 사용자 와 무관 한 내부 실현 디 테 일 을 숨 기 는 것 입 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기