모던 자바스크립트 Deep Dive - 45. 프로미스

둡둡·2024년 3월 21일

Modern Javascript Deep Dive

목록 보기
46/49

45.1. 콜백 패턴의 단점

45.1.1. 콜백 헬

  • 비동기 함수는 내부의 비동기로 동작하는 코드가 완료되지 않더라도 즉시 종료됨
  • 비동기 함수 내부의 비동기로 동작하는 코드에서 처리 결과를 외부로 반환하거나 상위 스코프의 변수에 할당하더라도 의도대로 동작하지 않음
      1. setTimeout 함수의 콜백 함수에서 상위 스코프의 변수에 값을 할당함
      1. setTimeout 함수는 콜백 함수를 호출 스케줄한 다음, 타이머 id 반환 후 즉시 종료됨
      1. 콜백 함수는 setTimeout 종료된 이후에 동작함
let g = 0;
setTimeout(() => { g = 100; }, 0);
console.log(g); // 0
  • 비동기 함수의 처리 결과(서버 응답 등)에 대한 후속 처리는 비동기 함수 내부에서 수행해야 함
    • 일반적으로 후속 처리를 수행하는 콜백 함수 전달
    • 성공/실패에 따른 각각 다른 콜백 함수 전달해야 함
  • 콜백 헬 (callback hell) : 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상

45.1.2. 에러 처리의 한계

try {
  setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
  // 에러를 캐치하지 못함
  console.error('캐치한 에러', e);
}
    1. setTimeout 는 실행 컨텍스트 생성 후 콜 스택에 푸시되어 실행
    1. 콜백 함수를 기다리지 않고 즉시 종료되어 콜 스택에서 제거됨
    1. 이후 콜백 함수가 차례대로 태스크 큐 > 콜 스택으로 푸시되어 실행됨
    1. 이미 setTimeout 함수는 콜 스택에서 제거된 상태
    1. 에러는 호출자 (caller) 방향으로 전파되므로, 실행 컨텍스트가 푸시되기 직전에 푸시된 실행 컨텍스트 방향으로 전파됨 -> setTimeout 가 아님
    1. setTimeout 함수의 콜백 함수가 발생시킨 에러는 setTimeout의 catch에서 캐치되지 않음
  • 위와 같은 문제들을 극복하기 위해 ES6 프로미스 (Promise) 도입

45.2. 프로미스 생성

  • Promise는 ECMAScript 사양에 정의된 표준 빌트인 객체 (호스트 객체 아님)
  • Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 인수로 전달 받음
    • ECMAScript 사양에서는 executor 함수
    • resolve, reject
  • 프로미스의 비동기 처리 상태 정보
    • pending : 아직 수행되지 않은 상태 (프로미스 생성 직후 기본 상태)
    • settled : 성공/실패 여부와 무관하게 pending이 아닌 비동기 처리가 수행된 상태
      • fulfilled : 성공 -> resolve 함수 호출
      • rejected : 실패 -> reject 함수 호출
      • settled 상태가 되면 다른 상태로 변화할 수 없음

  • 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체

45.3. 프로미스 후속 처리 메서드

45.3.1. Promise.prototype.then

  • then(callback1, callback2)
    • callback1 : fulfilled 상태가 되면 호출, 프로미스의 비동기 처리 결과를 인수로 전달 받음
    • callback2 : rejected 상태가 되면 호출, 프로미스의 에러를 인수로 전달 받음
  • 항상 프로미스 반환 : 콜백 함수가 프로미스가 아닌 값을 반환하면 암묵적으로 프로미스를 생성해 반환함

45.3.1. Promise.prototype.catch

  • catch(callback)
    • rejected 상태인 경우만 호출
    • then(undefined, onRejected) 동일하게 동작
  • 항상 프로미스 반환

45.3.1. Promise.prototype.finally

  • finally(callback)
    • 성공/실패 여부와 상관없이 무조건 한 번 호출됨
    • 공통적으로 수행이 필요한 경우 유용함
  • 항상 프로미스 반환

45.4. 프로미스 에러 처리

  • catch 메서드 호출시 내부적으로 then(undefined, onRejected) 호출
  • 에러 처리는 catch 메서드 사용 권장 (then 메서드의 두 번째 콜백 함수 X)
    • then 메서드 호출 이후에 catch 호출하면 then 내부에서 발생한 에러까지 모두 캐치 가능
    • 가독성이 좋고 명확함

45.5. 프로미스 체이닝

  • 프로미스 체이닝 (promise chaining) : 프로미스를 반환하므로 연속적으로 호출 가능
  • 프로미스 체이닝을 통해 비동기 처리 결과를 전달 받아 후속 처리하여 콜백 헬이 발생하지 않음
  • 단, 콜백 패턴을 사용하므로 가독성 좋지 않음
    • ES8 도입된 async/await 사용 가능
    • 프로미스의 후속 처리 없이 동기처럼 동작함

45.6. 프로미스 정적 메서드

45.6.1. Promise.resolve / Promise.reject

  • 프로미스를 생성하기 위해 이미 존재하는 값을 래핑하여 사용

45.6.2. Promise.all

  • 여러 개의 비동기 처리를 모두 병렬 처리할 때 사용
  • 전달받은 모든 프로미스가 모두 fulfilled 상태가 되면 처리 결과를 순서대로 배열에 저장해 프로미스로 반환
    • 처리 순서 보장
  • 최종 처리 시간은 가장 늦게 처리되는 프로미스 이후
    • 아래 예제에서는 request1의 3초보다 조금 더 소요됨
  • 하나라도 rejected 상태가 되면 즉시 종료
  • 인수로 전달받은 요소가 프로미스가 아닌 경우 프로미스로 래핑함
Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)),
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), 
  new Promise(resolve => setTimeout(() => resolve(3), 1000)),
])
  .then([response1, response2, response3] => 
         console.log(response1, respone2, respone3)) // 1, 2, 3
  .catch(console.error);

45.6.2. Promise.race

  • 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 프로미스 반환
  • 하나라도 rejected 상태가 되면 즉시 종료
Promise.race([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)),
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), 
  new Promise(resolve => setTimeout(() => resolve(3), 1000)),
])
  .then(console.log) // 3
  .catch(console.error);

45.6.2. Promise.allSettled

  • 성공/에러 여부와 상관 없이 모든 프로미스가 settled 상태가 되면 처리 결과를 배열로 반환
    • fulfilled 상태 : value 프로퍼티
    • rejected 상태 : reason 프로퍼티
  • ES11(ECMAScript 2020) 도입되어 IE 제외한 대부분 모던 브라우저에서 지원
Promise.allSettled([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)),
  new Promise((_, reject) => setTimeout(() => reject(new Error('Error!')), 2000)), 
])
  .then(console.log)
/*
[
  { status: "fulfilled", value: 1 },
  { status: "rejected", reason: Error: Error! }  
]
*/

45.7. 마이크로태스크 큐

  • 마이크로태스크 큐(microtask queue/job queue) : 프로미스 후속 처리 메서드의 콜백 함수 일시 저장
  • 태스크 큐보다 우선 순위 높음
    • 콜 스택이 비게 되면 마이크로태스크 큐 > 태스크 큐 순서로 가져와서 실행함

45.8 fetch

  • HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API
    • 프로미스 지원으로 콜백 패턴 단점 보완
    • IE 제외한 대부분 모던 브라우저에서 제공
  • fetch(url [, options])
    • HTTP 응답인 Response 객체를 래핑한 프로미스 객체 반환
    • Response 객체가 가지고 있는 메서드 사용 가능
  • 주의사항
    • 404, 500 HTTP 에러는 Response 객체의 ok=false 로 설정하여 resolve 반환
    • 네트워크 장애나 CORS 에러 등 일 때만 reject 반환
    • ok 상태 추가 체크 필요
    • axios는 모든 에러를 catch 처리 가능하므로 권장
  • cf. MDN - Using Fetch

[출처] 모던 자바스크립트, Deep Dive

profile
괴발개발라이프

0개의 댓글