[JS] 프로미스

21069 단어 JavaScriptJavaScript

비동기 함수

: 함수 내부에 비동기로 동작하는 코드를 포함한 함수

  • 비동기 함수 호출 시, 함수 내부의 비동기 코드가 완료되지 않아도 즉시 종료
    - 함수가 종료된 이후에 완료
    - 함수 내부의 비동기 코드의 처리 결과를 외부로 반환 or 상위 스코프 변수에 할당할 경우, 기대한 대로 동작하지 않음
    -> 비동기 함수의 처리 결과(서버의 응답 등)에 대한 후속 처리는 비동기 함수 내에서 수행해야 함
  • 비동기 함수에 비동기 처리 결과에 대한 후속처리를 수행하는 콜백 함수 전달하는 것이 일반적

콜백 함수

  • 비동기 처리를 위한 하나의 패턴

콜백 패턴의 단점

1) 콜백 헬

  • 비동기 처리 결과를 가지고 또다시 비동기 함수를 호출하는 경우
  • 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상
  • 가독성이 좋지 않음 -> 실수 유발의 원인

2) 에러 처리의 한계

  • 가장 심각한 문제점
  • 비동기 함수의 콜백 함수를 호출하는 호출자가 비동기 함수가 아니므로
    비동기 함수의 콜백 함수가 발생 시킨 에러를 잡지 못한다.
try {
  setTimeout(() => { throw new Error('Error!');	}, 1000);
} catch(e) {
  // setTimeout의 콜백 함수에서 발생한 에러를 캐치하지 못한다.
  console.error('캐치한 에러', e);
}

3) 여러 개의 비동기 처리를 한 번에 처리하는 데 한계

1. 프로미스

  • ES6에서 비동기 처리를 위한 또 다른 패턴
  • 표준 빌트인 객체 (호스트 객체 X)
  • 비동기 처리 상태와 처리 결과를 관리하는 객체
    -> 전통적인 콜백 패턴이 가진 단점 보완, 비동기 처리 시점 명확하게 표현 가능

2. 프로미스 생성

  • Promise 생성자 함수 + new 연산자 => 프로미스(Promise 객체) 생성
  • 인수: 비동기 처리를 수행할 콜백 함수
    - resolve: 비동기 처리 성공 시 호출되는 함수
    - reject: 비동기 처리 실패 시 호출되는 함수
const promise = new Promise((resolve, reject) => {
  if(/* 비동기 처리 성공 */) {
     resolve('result');
  } else { /* 비동기 처리 실패 */
    reject('failure reason');
  }
});
  • 프로미스의 상태 정보
    - pending: 비동기 처리 수행 전 = 프로미스 생성 직후
    - settled: pending이 아닌 상태 = 비동기 처리가 수행된 상태
    • 한 번 settled 상태가 되면 다른 상태로 변화할 수 X
      • fulfilled: 비동기 처리 수행(성공) = resolve 함수 호출 시
      • rejected: 비동기 처리 수행(실패) = reject 함수 호출 시

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

  • 프로미스의 비동기 처리 상태 변화에 따른 후속 처리
  • 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출됨
  • 항상 프로미스를 반환하며 비동기로 동작
    - 콜백 함수가 프로미스가 아닌 값을 반환하면, 암묵적으로 resolve or reject하여 프로미스를 생성해 반환

1) Promise.prototype.then

  • 첫 번째 콜백 함수: fulfilled 상태가 되면(resolve 함수가 호출되면) 호출
  • 두 번째 콜백 함수: rejected 상태가 되면(reject 함수가 호출되면) 호출
new Promise(resolve => resolve('fulfilled'))
	.then(v => console.log(v), e => console.log(e));	// fultilled

2) Promise.prototype.catch

  • rejected 상태인 경우에만 호출
  • then(undefined, onRejected)와 동일하게 동작
new Promise((_, reject) => reject(new Error('rejected')))
	.catch(e => console.log(e));	// Error: rejected

3) Promise.prototype.finally

  • 프로미스의 성공(fulfilled) / 실패(rejected) 와 상관없이 무조건 한 번 호출
  • 공통적으로 수행해야 할 처리 내용이 있을 때 유용

4. 프로미스의 에러 처리

  • 프로미스의 후속 처리 메서드 then의 두 번째 콜백 함수 or catch로 에러 처리 가능
    - catch 메서드에서 하는 것이 가독성이 좋고 명확함

5. 프로미스 체이닝

  • 후속 처리 메서드는 언제나 프로미스를 반환하므로 연속적으로 호출 가능
  • 비동기 처리 결과를 전달받아 후속 처리를 하므로 콜백 헬 발생 X
    -> 프로미스도 콜백 패턴을 사용하므로 가독성이 좋진 않음
    => ES8에서 도입된 async/await 사용하여 해결
    • 후속 처리 메서드 없이 (동기 처리처럼)프로미스가 처리 결과를 반환하도록 구현 가능
const url = 'https://...';

// 프로미스 체이닝 이용
promiseGet(`${url}/posts/1`)
	.then(({ userId }) => promiseGet(`${url}/users/${userId}`))
	.then(userInfo => console.log(userInfo))
	.catch(err => console.log(err));

// async/await 이용
(async () => {
  const { userId } = await promiseGet(`${url}/posts/1`);
  const userInfo = await promiseGet(`${url}/users/${userId}`);
  
  console.log(userInfo);
})();

6. 프로미스의 정적 메서드

1) Promise.resolve / Promise.reject

  • 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log);	// [1, 2, 3]

// 위 예제와 동일하게 동작
const resolvedPromise = new Promise(resolve => resolve([1, 2, 3]));
resolvedPromise.then(console.log); // [1, 2, 3]

2) Promise.all

  • 여러 개의 비동기 처리를 모두 병렬 처리할 때 사용
  • 이터러블을 인수로 전달 받음
  • 모든 프로미스가 fulfilled 상태가 되면 모든 처리 결과를 배열에 저장해 새로운 프로미스 반환
  • 처리 순서 보장
  • 프로미스 하나라도 reject 상태가 되면 즉시 종료
const requestData1 = () => new Promise(resolve => setTimeout(() => resolve(1), 3000));
const requestData2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000));
const requestData3 = () => new Promise(resolve => setTimeout(() => resolve(3), 1000));

Promise.all([requestData1(), requestData2(), requestData3()])
	.then(console.log)	// [1, 2, 3]
	.catch(console.error);

3) Promise.race

  • 이터러블을 인수로 전달 받음
  • Promise.all과 비슷하나 가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 새로운 프로미스를 통해 반환

4) Promise.allSettled

  • 이터러블을 인수로 전달 받음
  • 모든 프로미스가 settled 상태(fulfilled or rejected)가 되면 처리 결과를 배열로 반환

7. 마이크로태스크 큐

  • 프로미스의 후속 처리 메서드(then, catch, finally)의 콜백함수가 저장되는 곳
  • 비동기 함수의 콜백 함수 or 이벤트 핸들러는 태스크 큐에 일시 저장
  • 우선 순위: 마이크로태스크 큐 > 태스크 큐
    - 마이크로태스크 큐가 비면 태스크 큐에서 대기하고 있는 함수를 실행시킴

8. fetch

  • XMLHttpRequest 객체와 마찬가지로 HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API
  • 사용법이 간단하고 프로미스를 지원
  • HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환

좋은 웹페이지 즐겨찾기