20210721 JavaSciprt DeepDive 17 : 프로미스, fetch, 제너레이터, async/await, 에러 처리
JavaScript Deep Dive 17
📄 용어 및 중요사항 정리
프로미스
비동기 함수: 함수 내부에 비동기로 동작하는 코드를 포함한 함수- 비동기 함수 내부의 비동기 동작 코드는 비동기 함수가 종료된 이후에 완료됨
 - 비동기 동작 코드에서 처리 결과를 외부로 반환하거나, 상위 스코프의 변수에 할당 하면 기대한 대로 동작하지 않음
 - 비동기 함수의 처리 결과에 대한 후속 처리는 비동기 함수 내부에서 외부에서 콜백 함수를 받아 수행해야 함 -> 콜백 헬 발생
 - 콜백 헬
        
- 에러 처리 한계 : 에러는 호출자 방향으로 전파되지만, 비동기 함수의 콜백은 비동기 함수가 호출하지 않음 -> 콜백에 대한 에러는 catch 되지 않음
 
 
프로미스:- 콜백 헬을 극복하기 위해 도입됨
 - 표준 빌트인 객체
 - 비동기 처리 상태와 철리 결과를 관리하는 객체
 - new Promise 생성자 함수로 생성
 - 비동기 처리를 수행할 콜백 함수를 인수로 전달 받음
        
- 비동기 처리 후속 콜백 resolve(성공), reject(실패) 콜백 함수를 인수로 받음
 
 - 비동기 상태 정보 (status)
        
- pending : 비동기 처리가 수행되지 않은 상태
 - settled : fulfilled(성공), rejected(실패)
 
 - 비동기 결과 (result)
        
- undefined -> value or error
 
 
const promise = new Promise((resolve, reject) => {
  if (/* 비동기 처리 성공 조건 */) {
    // 비동기 성공시 호출할 함수
    resolve('result')
  } else {
    // 비동기 실패시 호출할 함수
    reject('failure reason')
  }
})
프로미스의 후속 처리 메서드
Promise.prototype.then(resolveCallback, rejectedCallback)- resolveCallback : fulfilled 상태시 프로미스의 비동기 처리 결과를 인수로 받음
 - rejectedCallback : rejected 상태시 프로미스의 에러를 인수로 받음
 - return : 언제나 프로미스 반환
 
Promise.prototype.catch(callback)- rejected 상태인 경우 호출 됨
 - callback : error 처리
 - return : 언제나 프로미스 반환
 
Promise.prototype.finally(callback)- 프로미스 성공 실패 상관 없이 무조건 한번 호출
 - return : 언제나 프로미스 반환
 
프로미스의 에러 처리
- 방법01 : then 메서드의 두번째 메서드로 에러 처리
    
- 프로미스 체이닝시 다른 then 메서드에서 발생하는 에러는 캐치하지 못하여, 코드 복잡해지고 가독성이 떨어짐
 
 - 방법02 : catch 메서드 (추천)
    
- 모든 then 메서드 호출한 이후 호출시 비동기 처리 에러, then 메서드 내부의 에러까지 모두 캐치 가능
 
 
프로미스 체이닝
- then, catch, finally 후속 처리 메서드는 언제나 프로미스를 반환하므로, 연속 호출 가능 함
 - 후속 처리 메서드의 콜백 함수가 프로미스가 아닌 값을 반환해도, 암묵적으로 resolve, reject하여 프로미스를 생성해 반환함
 
프로미스의 정적 메서드
Promise.resolve/reject(value): 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용
// Promise.resolve/reject를 활용한 방식
const resolvePromise = Promise.resolve([1, 2, 3]);
resolvePromise.then(console.log); // [1, 2, 3]
const rejectPromise = Promise.reject(new Error("Error!"));
rejectedPromise.catch(console.log); // Error: Error!
// 기존 방식
const resolvePromise = new Promise((resolve) => resolve([1, 2, 3]));
resolvePromise.then(console.log); // [1, 2, 3]
const rejectPromise = new Promise((_, reject) => reject(new Error("Error!")));
rejectedPromise.catch(console.log); // Error: Error!
Promise.all([asyncFunc1, asyncFunc2, asyncFunc3, ...])- 여러개의 비동기 처리를 모두 병렬 처리할 때 사용함
 - 배열 등의 이터러블을 인수로 전달 받음
 - 모든 프로미스가 모두 fulfilled 상태가 되면 모든 처리 결과를 배열에 저장해 새로운 프로미스를 반환함
 - 처리한 결과 값은 넣은 비동기 처리 함수 순서 차례 대로 배열에 저장함(완료 순서X)
 - 하나라도 rejected 상태가 되면 나머지 프로미스 fulfiled 상태를 기다리지 않고 즉시 종료 됨
 - 이터러블의 요소가 프로미스가 아닌 경우, Promise.resolve 메서드로 프로미스로 래핑 함
 - map 메서드를 통해서 여러 반복 작업을 비동기 적으로 처리 할 수 있음
 
Promise.race([asyncFunc1, asyncFunc2, asyncFunc3, ...])- 배열 등의 이터러블을 인수로 전달 받음
 - 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환
 - 하나라도 rejected 상태가 되면 나머지 프로미스 fulfiled 상태를 기다리지 않고 즉시 종료 되고, 해당 rejected 프로미스를 반환
 
Promise.allSettled([asyncFunc1, asyncFunc2, asyncFunc3, ...])- 배열 등의 이터러블을 인수로 전달 받음
 - 전달 받은 프로미스가 모두 settled 상태가 되면 처리 결과를 배열로 반환함
 - fulfilled, rejected 상태 상관 없이 모든 프로미스들의 처리 결과를 나타내는 객체가 담겨 있음
        
- 처리 결과 객체의 프로퍼티
            
- fulfilled -> status, value
 - rejected -> status, reason
 
 
 - 처리 결과 객체의 프로퍼티
            
 
마이크로 태스크 큐
- 후속 처리 메서드의 콜백 함수는 태스크 큐가 아니라 마이크로태스큐에 저장 됨
 - 마이크로 태스크 큐는 태스크 큐와 별도의 큐 임
 - 태스크 큐보다 우선순위가 높음
 - 이벤트 루프는 마이크로 태스크 큐의 함수를 먼저 가져와서 실행함
    
- 마이크로 태스크 큐가 비면 그때 태스크 큐에서 함수를 가져와 실행 함
 
 
fetch
- XMLHttpRequest 객체와 마찬가지로 HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API
 - fetch 함수는 사용법도 간단하고, 프로미스를 지원함
 fetch(url [, options])- HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환함
 - URL만 전달하면 GET 요청을 전송함
 - then을 통해서 fatch 함수가 resolve한 Response 객체를 전달 받을 수 있음
 - Response 객체의 prototype 메서드가 존재함
        
Response.prototype.json(): MIME 타입 - application/json인 응답 몸체 취득- 취득시 역직렬화 시켜줌
 
 url: HTTP 요청을 전송할 URLoptions: options 객체- method : ‘POST’, ‘PATCH’, ‘DELETE’
 - headers : {‘accept or content-Type’:’MIME type’}
 - body : 요청 전송할 body
 
// fetch를 이용한 요청 함수 만들기
const request = {
  get(url) {
    return fetch(url);
  },
  post(url, payload) {
    return fetch(url, {
      method: "POST",
      headers: { "content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
  },
  patch(url, payload) {
    return fetch(url, {
      method: "PATCH",
      headers: { "content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
  },
  delete(url) {
    return fetch(url, { method: "DELETE" });
  },
};
제너레이터
제너레이터(generator): 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수- 특징
        
- 함수 호출자에게 함수 실행의 제어권을 양도(yield)할 수 있음(함수 호출자가 함수 실행 제어 가능)
 - 함수 호출자에게 상태를 전달할 수 있고 함수 호출자로부터 상태를 전달 받을 수도 있음
 - 제너레이터 함수 호출시 이터러블이면서 동시에 이터레이터인 제너레이터 객체를 반환함 (함수 코드 실행 X)
 
 
- 특징
        
 
function*,yield사용- 화살표 함수로 정의 불가
 - new 연산자와 함꼐 생성자 함수로 호출 불가
 
제너레이터 객체
- 제너레이터 객체는 이터러블이면서 동시에 이터레이터 임
 - 제너레이터 객체는 next 메서드를 가지는 이터레이터 이므로 Symbol.iterator 메서드를 호출해서 별도로 이터레이터를 생성할 필요가 없음
 제너레이터 객체 메서드next(): 제너레이터 함수의 yield 표현식 까지 코드 블록을 실행하고, yield된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환- 인수를 넣으면 yield 표현식을 할당받는 변수에 할당 됨
 - 처음 호출되는 next는 인수를 넣어도 무시됨
 
return(value): 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체 반환throw(error): 전달 받은 에러를 발생시키고 undefined를 value 프로퍼티 값, true를 done 프로퍼티 값으로 갖는 이터레이터 result 객체를 반환
제너레이터의 일시 중지와 재개
- yield 키워드와 next 메서드를 통해 실행 중지와 재개를 할 수 있음
 - next 메서드 호출시 yield 표현식 까지 실행되고 일시 중지 됨 -> 함수 제어권 호출자로 양도
 - next 호출시 반환된 result 객체에는 yield 표현식 뒤에 값이 value 프로퍼티에 할당되고, done 프로퍼티에 제너레이터 함수가 끝까지 실행 되었는지 불리언 값 할당
 
function* getFunc() {
  const x = yield 1;
  const y = yield x + 10;
  return x + y;
}
const generator = getFunc();
let res = generator.next();
console.log(res); //  {value: 1, done: false}
res = getnerator.next(10);
console.log(res); //  {value: 20, done: false}
res = getnerator.next(20);
console.log(res); //  {value: 30, done: true}
제너레이터의 활용
간단한 이터러블의 구현
// 기존 이터러블 구현
const infiniteFibonacci = (function () {
  let [pre, cur] = [0, 1]
  return {
    [Symbol.iterator]() { return this}
    next() {
      [pre, cur] = [cur, pre + cur];
      return {value: cur}
    }
  }
})()
// 제너레이터를 이용한 이터러블 구현
const infiniteFibonacci = (function* () {
  let [pre, cur] = [0, 1]
  while (true) {
    [pre, cur] = [cur, pre + cur]
    yield cur
  }
}())
비동기 처리- 비동기 처리를 동기 처리 처럼 구현 가능
 - 제너레이터 실행기가 필요하면 co 라이브러리 사용 추천
 
// 제너레이터 실행기
const async = (generatorFunc) => {
  const generator = generatorFunc();
  const onResolved = (arg) => {
    const result = generator.next(arg);
    return result.done
      ? result.value
      : result.value.then((res) => onResolved(res));
  };
  return onResolved;
};
async(function* fetchTodo() {
  const url = "https://jsonplaceholder.typicode.com/todos/1";
  const response = yield fetch(url);
  const todo = yield response.json();
  console.log(todo);
})();
async/await
- 후속 처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속으로 처리할 필요 없이 동기 처리 처럼 프로미스를 사용할 수 있게 해줌
 
async 함수- async 키워드를 사용해 정의한 함수로, 언제나 프로미스를 반환 함
 - 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 resolve하는 프로미스를 반환함
 
// async 함수 선언문
async function foo(n) {
  return n;
}
// async 함수 표현식
const bar = async function (n) {
  return n;
};
// async 화살표 함수
const baz = async (n) => n;
// async 메서드
const obj = {
  async foo(n) {
    return n;
  },
};
// async 클래스 메서드
class MyClass {
  async bar(n) {
    return n;
  }
}
await 키워드- 프로미스가 settled 상태가 될 때까지 대기하다가 settled 상태가 되면 resolved한 처리 결과 반환
 - await는 프로미스 앞에서 사용해야 함
 - 서로 연관 되지 않은 프로미스의 경우 시간을 더 잡아 먹으므로 , 
await Promise.all을 사용해 처리함 
에러 처리- 원래 비동기 함수의 콜백 함수를 호출한 것은 비동기 함수가 아니기 때문에 try…catch 문을 사용해 에러를 캐치할 수 없음
 - async/await 에서 try…catch 문으로 에러를 모두 캐치 할 수 있음
        
- 프로미스를 반환하는 비동기 함수는 명시적으로 호출 가능하여 호출자가 명확함
 
 - async 함수 내에서 catch 문을 사용하지 않으면, async 함수는 발생한 에러를 reject하는 프로미스를 반환함
 
에러 처리
- 에러가 발생하지 않는 코드를 작성하는 것은 불가능 하고, 에러 발생시 프로그램은 강제 종료 됨
 - try…catch 문을 통해서 프로그램 강제 종료를 막을 수 있음
 
예외 처리: 직접적으로 에러를 발생하지는 않는 상황- 예외적인 상황에 의해서 다른 코드가 에러를 발생할 수 있음
 - 예) querySelector의 경우 없으면 null을 반환함 -> null을 할당 받은 변수에 프로퍼티를 참조하여 사용하는 경우 error 발생
        
- querySelector의 반환 값을 단축 평가 또는 옵셔널 체이닝 연산자 사용해 예외 처리 하거나, 에러 처리 코드를 미리 등록해 두고 에러 발생시 에러 처리 코드로 점프 되도록 하는 방법
 
 
try…catch…finally 문
- 에러 처리(error handling) : try…catch…finally 문을 사용하여 에러를 처리하는 것
    
- try : 에러가 발생할 가능성이 있는 코드 블럭
 - catch : try에서 에러 발생시 실행되는 코드 블럭 (try에서 발생한 Error 객체가 전달됨)
 - finally : 에러 발생과 상관 없이 반드시 한번 실행되는 코드 블럭
 
 
Error 객체
new Error(errorMessage)- errorMessage: 에러를 상세히 설명하는 에러 메세지(string)를 인수로 받음
 
message 프로퍼티: Error 생성자 함수에 전달한 인수를 값으로 갖는 프로퍼티stack 프로퍼티: 에러를 발생시킨 콜스택의 호출 정보를 나타내는 문자열 (디버깅 목적)에러 객체 종류 7가지- Error : 일반적인 에러 객체
 - SyntaxError : JS 문법에 맞지 않는 문 해석시 발생하는 에러 객체
 - ReferenceError : 참조 함수 없는 식별자를 참조시 발생하는 에러 객체
 - TypeError : 피연산자 또는 인수의 데이터 타입이 유효하지 않을 때
 - RangeError : 숫자값의 허용 범위를 벗어났을 때 발생
 - URIError : encodeURI 또는 decodeURI 함수에 부적절한 인수 전달시 발생
 - EvalError : eval 함수에서 발생하는 에러 객체
 
thorw 문
- 해당 에러 객체로 에러를 발생 시킴
 - try 문에 error 발생시, catch 문에 error 변수가 생성되고 해당 error 객체가 할당됨
 
에러 전파
- throw된 에러를 캐치 하지 않으면, 에러는 호출자 방향으로 전파 됨 (콜 스택의 아래 방향으로 전파됨)
 - 비동기 함수, 프로미스 후속 처리 메서드의 콜백 함수는 호출자가 없기 때문에 에러 전파가 되지 않음