[에러핸들링]

1. try .. catch 문법

에러가 나도 에러페이지가 아닌, 유연하고 괜찮은 처리를 위해 사용

  • 알고리즘 1) 먼저, try {…} 안의 코드가 실행. 2) 에러가 없다면, catch 블록은 건너뜀. 3) 에러가 있다면, try 안 코드의 실행이 중단되고, catch(err) 블록으로 제어 흐름이 넘어감. 변수 err(아무 이름이나 사용 가능)는 무슨 일이 일어났는지에 대한 설명이 담긴 에러 객체를 포함.

try.. catch 는 오직 runtime에만 동작한다. setTimeout 안에서 try catch를 써주면서 에러를 잡을 수 있다.

  • 에러객체 catch(err) err이 에러 객체이다. () name 에러 이름. ** 정의되지 않은 변수 일시 “ReferenceError”가 이름이 됨. () message 에러 상세 내용

(*) stack 현재 호출 스택. 에러를 유발한 중첩 호출들의 순서 정보를 가진 문자열 디버깅 목적으로 사용되었으므로

  • throw 연산자 에러 생성해주는 연산자

자바스크립트는 Error, SyntaxError, ReferenceError, TypeError등의 표준 에러 객체 관련 생성자를 지원해줌. 내장 에러 객체의 name 프로퍼티 : 생성자 이름과 동일. 프로퍼티 message : 생성자 인수

아래와 같음

  let error = new Error("이상한 일이 발생했습니다. o_O");

  alert(error.name); // Error
  alert(error.message); // 이상한 일이 발생했습니다. o_O

throw 연산자의 사용법은 다음과 같다.

  if (!user.name) {
    throw new SyntaxError("불완전한 데이터: 이름 없음"); // (*)
  }

위와 같이 에러를 생성해서 던질 수 있는 것이다.

  • 에러 다시 던지기 catch로 에러를 잡았는데 또 다른 에러가 뜨는 경우 ‘다시 던지기 (rethrowing) 기술’을 사용해야 함!
  function readData() {
    let json = '{ "age": 30 }';
  
    try {
      // ...
      blabla(); // 에러!
    } catch (e) {
      // ...
      if (!(e instanceof SyntaxError)) {
        throw e; // 알 수 없는 에러 다시 던지기
      }
    }
  }
  
  try {
    readData();
  } catch (e) {
    alert( "External catch got: " + e ); // 에러를 잡음
  }

catch 블록에서 처리하고자 하는 에러 종류를 if문으로 받아서 처리한 후 그 에러가 아니면 throw를 써서 다시 던진다. 또다른 try catch 구문을 사용하여 다른 에러를 잡아낸다.

  • try … catch … finally
    try {
      ... 코드를 실행 ...
      } catch(e) {
      ... 에러 핸들링 ...
      } finally {
      ... 항상 실행 ...
      }
    

    finally 절은 실행결과에 상관 없이 실행하고 싶을 경우 사용됨. 대표적으로 연산시간 측정을 위해 사용됨

      try {
    result = fib(num);
      } catch (e) {
    result = 0;
      } finally {
    diff = Date.now() - start;
      }
    
  • return 과 finally ```javascript try { return 1;

} catch (e) { /* … */ } finally { alert( ‘finally’ ); }

  함수 블록 안에서 try 문에서 return이 나와 함수를 빠져나가게 될 때
  이럴 때는 return 실행되기 전에 일단 finally가 실행되고 return이 실행된다.

  - 전역 catch 
  해당 블록에서만 에러 잡지 않고 전체 코드에서 에러를 잡고 싶을 경우 사용
```javascript
  window.onerror = function(message, url, line, col, error) {
    // ...
  };

1) message 에러 메시지 2) url 에러가 발생한 스크립트의 URL 3) line, col 에러가 발생한 곳의 줄과 열 번호 4) error 에러 객체

이런게 있고, 전역 핸들러의 사용은 에러의 유용한 대응을 위해서라기 보다는 사용자가 사용중 발생하는 에러를 개발자에게 보내기 위한 용도 정도이다.

  1. 커스텀 에러와 에러확장 에러 종류를 개발자가 만들 수 있다는 것이다. 지금 만들어진 에러만 말고 좀 더 특정화 된 에러 명칭을 만듬으로써 유연한 파악과 대응이 되기 때문이다. throw 연산자에는 제약이 없으므로 Error 클래스를 상속받아 사용하는 것이 일반적이다.
  • 에러 확장하기 유효한 json 형태
    let json = `{ "name": "John", "age": 30 }`;
    

readUser(json)은 JSON 형식의 데이터를 읽는 기능 뿐아니라 ‘검증’기능 있어야 함. 검증 : 필수 프로퍼티가 없거나, 형식에 맞지 않으면 에러를 발생. (ValidationError를 만들거임)

Error를 상속받은 커스텀 에러

    class ValidationError extends Error {
      constructor(message) {
        super(message); // (1)
        this.name = "ValidationError"; // (2)
      }
    }

message는 부모에게서 받아오고 name은 재설정 해줌. constructor 는 class 상속에서 배웠지만 일단 먼저 super를 써서 부모에게서 데이터를 받아야함.

실제 사용할 때 try catch 문 안에서…

    if (err instanceof ValidationError) {
      alert("Invalid data: " + err.message); // Invalid data: No field: name
    }

위의 구문을 사용하여 ValidationError 를 잡아낸다. else 문을 붙여서 재던지기를 해주는 것은 안정성을 높일 수 있을 것임

  • 더깊게 상속하기 근데 위의 경우 ValidationError 에선 실제 검증해주는 과정은 없고 이름만 바꿔준 것이다.

    그래서 다음과 같이 PropertyRequiredError를 만들어 상속받을 것이다.

    class PropertyRequiredError extends ValidationError {
      constructor(property) {
        super("No property: " + property);
        this.name = "PropertyRequiredError";
        this.property = property;
      }
    }

여기서 this.name에다가 매번 에러 클래스를 만들 때마다 이름을 붙여주는게 귀찮은 작업이기 때문에 다음과 같이 MyError 라는 클래스 안에서 this.name = this.constructor.name 으로 지정해주면 알아서들 만들어진다.

    class MyError extends Error {
      constructor(message) {
        super(message);
        this.name = this.constructor.name;
      }
    }
    
    class ValidationError extends MyError { }
    
    class PropertyRequiredError extends ValidationError {
      constructor(property) {
        super("No property: " + property);
        this.property = property;
      }
    }
그러면 에러 클래스도 더 깔끔하게 만들 수 있다. 
위의 코딩은 this를 동적으로 받기 때문에 가능한 일이다.
  • 예외 감싸기 “미래에 우리가 만든 함수의 기능이 커지면서 에러 종류가 많아질 텐데 그때마다 에러 종류에 따라 에러 처리 분기문을 매번 추가해야 하나?” 하는 의문이들었다면… 일단 그에 대한 답 보통은 그렇지 않다는 것이다. 함수 자체가 세분화 되어있기도하고, 또 사용자에게 보여줘야 하는 애들을 catch로 잡는 것이기 때문에 출력쪽 함수에만 쓰면 되기 때문임. // 이 부분 왜 이렇게 굳이 하는 건지 잘 모르겠음.

[프라미스와 async, await]

1. 콜백

비동기 동작 : 원하는 때에 동작이 시작하도록 할 수 있음. 자바스크립트 호스트 제공하는 함수 사용해서 비동기 동작 가능

  function loadScript(src) {
    // <script> 태그를 만들고 페이지에 태그를 추가합니다.
    // 태그가 페이지에 추가되면 src에 있는 스크립트를 로딩하고 실행합니다.
    let script = document.createElement('script');
    script.src = src;
    document.head.append(script);
  }
  loadScript(src) <script src=""> 동적으로 만들고 실행한다.

그런데 loadscript(src)를 불러오고 곧바로 그 스크립트 안에 함수를 불러오면 에러가 뜬다. 예시)

  loadScript('/my/script.js'); // script.js엔 "function newFunction() {…}"이 있습니다.
  newFunction(); // 함수가 존재하지 않는다는 에러가 발생합니다!

스크립트 읽어오는 시간을 충분히 확보하게 해줘야 함. 근데 그 충분한 시간을 확보했는지 못했는지 어떻게 알 수 있는가. 스크립트 로딩이 끝났는지 여부를 알고 싶을 때.

loadScript의 두번째 인수로 콜백함수를 넣어서. loadScript가 끝나면 콜백함수를 발생하도록 만듬.

  loadScript('/my/script.js', function() {
    // 콜백 함수는 스크립트 로드가 끝나면 실행됩니다.
    newFunction(); // 이제 함수 호출이 제대로 동작합니다.
    ...
  });

대개 두번째 인수로 전달 된 함수는 익명 함수로 전달 된다.

  • 콜백 속 콜백 스크립트 다음으로 스크립트 불러오고 싶은 경우 ```javascript loadScript(‘/my/script.js’, function(script) {

    alert(${script.src}을 로딩했습니다. 이젠, 다음 스크립트를 로딩합시다.);

    loadScript(‘/my/script2.js’, function(script) { alert(두 번째 스크립트를 성공적으로 로딩했습니다.); });

});

  위와 같이 loadScirpt 불러오고 콜백함수 불러오는 그 안에 또 loadScript하고 또 콜백
  몇개든 중첩해서 쓸 수 있음
  근데 이렇게 하는 것은 동작이 많은 경우에는 당연히 안좋겠지 다른 방법을 주로 쓴다함.

  - 에러 핸들링
```javascript
  script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));

위의 코딩을 추가하여 에러 핸들링 해주고 사용법은 아래와 같다.

  loadScript('/my/script.js', function(error, script) {
    if (error) {
      // 에러 처리
    } else {
      // 스크립트 로딩이 성공적으로 끝남
    }
  });

먼저 if 문으로 에러인경우 에러처리 해주고, else로 동작 실행해준다. 이런 패턴을 ‘오류 우선 콜백’ 이라고 한다.

  • 멸망의 피라미드 중첩해서 여러번 쓰는 경우를 멸망의 피라미드라고 한단다.

그래서 콜백함수를 독립적으로 정의하여 다음과 같이 코딩하면 완화가 된다.

  loadScript('1.js', step1);

  function step1(error, script) {
    if (error) {
      handleError(error);
    } else {
      // ...
      loadScript('2.js', step2);
    }
  }

  function step2(error, script) {
    if (error) {
      handleError(error);
    } else {
      // ...
      loadScript('3.js', step3);
    }
  }

  function step3(error, script) {
    if (error) {
      handleError(error);
    } else {
      // 모든 스크립트가 로딩되면 다른 동작을 수행합니다. (*)
    }
  };

이렇게 하면 깊은 중첩 없이 콜백을 구현할 수 있다. 그래도 뭔가 보기가 힘들다. 더 나은 무언가가 필요한듯 하다.

promise를 배우는 이유이다.

1. 프라미스

팬 -> 가수 “언제 신곡 나와요?” 매번 이렇게 하는 것에 답하면 너무 비효율이니까 팬(정보-Email등…)을 받아서 가수 -> 팬 “신곡 나왔어!” (구독리스트) 이렇게 해주는 게 좋다는 것이다.

‘제작 코드(producing code)’ : 원격에서 스크립트를 불러오는 것 (시간이 걸리는 일을 진행). 위 비유에선 ‘가수’ ‘소비 코드(consuming code)’ : ‘제작 코드’의 결과를 기다렸다가 이를 소비. 이때 소비 주체(함수)는 여럿이 될 수 있음. 위 비유에서 ‘팬’

프라미스(promise) : ‘제작 코드’와 ‘소비 코드’를 연결해 주는 특별한 자바스크립트 객체임. 위 비유에서 ‘구독 리스트’.

‘프라미스’는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 ‘제작 코드’가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해줌.

  • 프라미스를 만드는 방법
      let promise = new Promise(function(resolve, reject) {
    // executor (제작 코드, '가수')
      });
    
  • executor new Promise에 전달되는 함수를 executor(실행자, 실행 함수) 라고 한다. executor는 new Promise가 만들어질 때 자동으로 실행됨 결과를 최종적으로 만들어내는 제작 코드를 포함. 위 비유에서 ‘가수’

  • executor의 인수 resolve와 reject 자바스크립트가 자체적으로 제공하는 콜백임. 개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 됨. ※ 대신 executor에선 결과 얻으면 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 함! //resolve(value) — 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출 //reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

  • promise 객체의 내부 프로퍼티 //state — 처음엔 “pending”(보류)이었다 resolve가 호출되면 “fulfilled”, reject가 호출되면 “rejected”로 변합니다. //result — 처음엔 undefined이었다, resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.

따라서 executor는 아래 그림과 같이 promise의 상태를 둘 중 하나로 변화시킵니다.