모던 자바스크립트 Deep Dive - 45장

박상은·2021년 10월 19일
0

Promise

1. Promise 등장 이유

콜백 함수를 사용하는 이유는 데이터를 받아오는 동안 다른 처리를 실행하고 받아온 데이터를 이후에 사용하기 위함이다.

  • 간단 예시
// 비동기 함수 대체
setTimeout(() => {
  // result를 특정한 비동기처리 이후에 받아온 값이라고 가정
  const result = 10;
}, 1000);

위와 같은 비동기 함수를 실행할 때 해당 함수 내부에서 결과로 받아온 값(result)를 콜백 함수 외부에서 사용할만한 방법이 필요함

  1. 전역변수에 넣어서 사용하기 ( 불가능 )
let tempValue = null;

// 비동기 함수 대체
setTimeout(() => {
  // result를 특정한 비동기처리 이후에 받아온 값이라고 가정
  const result = 10;
  tempValue = result;
}, 1000);

// 1) 여기서 사용
console.log(tempValue);		// null

비동기 함수로부터 얻은 결괏값을 전역변수에 넣고 사용하려고 했지만 실제로 작성해 보면 null값이 나옴

자바스크립트 작동 방식에 의해서 비동기 함수의 콜백 함수는 절 때 1)에서 보다 먼저 작동할 수가 없음
( 1)의 작동이 끝나고 난 후에 콜백 함수가 실행되는 순서가 뒤바뀔 수가 없음 )

  1. 콜백함수 이용하기
// setTimeout의 콜백함수
const callback = f => {
  const result = 20;
  f(result)
};

// setTimeout의 콜백함수의 결과값을 사용하기위한 콜백함수
const callbackOfCallback = v => console.log(v);

setTimeout(callback, 1000, callbackOfCallback);		// 20

// 분리안하고 사용
setTimeout(f => {
  const result = 20;
  f(result)
}, 1000, v => console.log(v));

비동기 함수에 콜백 함수(callbackOfCallback())를 인자로 넘겨줘서 비동기 함수의 처리가 완료될 시점에 콜백 함수(callbackOfCallback())를 호출해서 데이터를 사용하는 방법임

물론 정상적으로 처리는 되지만 가독성이 너무 안 좋음

또한 콜백함수로 처리할 경우에는 예외처리에 한계가 발생함

try{
  setTimeout(() => throw new Error("강제 에러 발생"));
} catch (error) {
  // 여기서 예외를 잡지 못함
  console.error(error);
}

이러한 이유때문에 Promise가 도입되었다.

2. Promise

  • 기본 형태
const promise = new Promise((resolve, reject) => {
  if(성공시)	resolve("성공한 값");
  else	reject("실패한 에러");
});

2.1 [[PromiseState]]

모든 프로미스들은 [[PromiseState]]로 내부 상태를 가진다.
1. pedding: 반환안한 대기 상태 ( resolve(), reject()중 아무것도 안한 상태 )
2. fulfilled: 성공을 선언한 상태 ( resolve() )
3. rejected: 실패를 선언한 상태 ( reject() )

2.2 [[PromiseResult]]

resolve(), reject()의 인수로 전달한 값을 저장하는 내부 프로퍼티

2.3 then()

[[PromiseState]]fulfilled이면 호출되며 첫 번째 인자로 [[PromiseResult]]값을 받음
그리고 프로미스체이닝을 위해서 반환 값을 Promise로 변환후 반환한다.

2.4 catch()

[[PromiseState]]rejected이면 호출되며 첫 번째 인자로 [[PromiseResult]]값을 받음
그리고 프로미스체이닝을 위해서 반환 값을 Promise로 변환후 반환한다.

2.5 finally()

무조건 한번 호출

2.6 예시

const promiseResolve = new Promise((resolve, reject) => resolve("a"));
const promiseReject = new Promise((resolve, reject) => reject(new Error("에러 발생")));

// 1)의 v에는 최초 promiseResolve에서 resolve("a")한 값인 "a"가 들어감
// 이후에는 그 이전에 반환한 값이 v에 들어가는 형식임
// 그리고 1)에서는 명시적으로 프로미스를 생성했지만, 기본 값을 반환하는 경우 체이닝을 위해 암묵적으로 프로미스로 변환후 반환해줌
// 즉 1)의 코드와 2), 3)의 코드는 동일한 코드임
promiseResolve
  .then(v => new Promise(r => r(v + "b")))		// 1)	// "a" + "b"반환
  .then(v => v + "c")		// 2)	// "ab" + "c"반환
  .then(v => v + "d")		// 3)	// "abc" + "d"반환
  .then(console.log);		// 최종 결과값 : abcd

promiseReject
  .then(v => v + 1)
  .then(v => v + 1)
  .then(v => v + 1)
  .then(console.log)
  .catch(error => console.error(error));	// reject호출이므로 앞의 then모두 무시하고 여기 실행

3. Promise 정적 메서드

  • 성공한 프로미스: [[PromiseState]]fulfilled인 프로미스
  • 실패한 프로미스: [[PromiseState]]rejected인 프로미스

3.1 Promise.resolve()

인수로 넘긴 값으로 성공한 프로미스 반환

3.2 Promise.reject()

인수로 넘긴 값으로 실패한 프로미스 반환

3.3 Promise.all()

인수로 프로미스 배열을 넘기고 해당 배열안의 모든 프로미스가 성공할 때 까지 기다렸다가 전부 성공시 성공값을 배열로 반환
하지만 하나라도 실패시 즉시 종료하고 실패한 값 반환

3.4 Promise.race()

인수로 프로미스 배열을 넘기고 가장 먼저 성공한 프로미스만 반환
중간에 실패할 경우 즉시 실패한 프로미스 반환

3.5 Promise.allSettled()

인수로 프로미스 배열을 넘기고 모든 프로미스가 완료될 때 까지 기다렸다가 값 배열로 반환
중간에 실패하더라도 멈추지 않고 모든 값을 받아서 반환함

Promise.resolve("a").then(console.log);		// "a"
Promise.reject(new Error("Error 발생!")).catch(console.error);	// error

Promise.all([
  new Promise(r => setTimeout(() => r(1000), 1000)),
  new Promise(r => setTimeout(() => r(2000), 2000)),
  new Promise(r => setTimeout(() => r(3000), 3000)),
  new Promise(r => setTimeout(() => r(4000), 4000)),
  // Promise.reject(new Error("all Error"))
]).then(console.log);	// 4초뒤에 [1000, 2000, 3000, 4000]

Promise.race([
  new Promise(r => setTimeout(() => r(1000), 1000)),
  new Promise(r => setTimeout(() => r(2000), 2000)),
  new Promise(r => setTimeout(() => r(3000), 3000)),
  new Promise(r => setTimeout(() => r(4000), 4000)),
  // Promise.reject(new Error("all Error"))
]).then(console.log);	// 1초뒤에 1000 / 4초뒤 종료

Promise.allSettled([
  new Promise(r => setTimeout(() => r(1000), 1000)),
  new Promise(r => setTimeout(() => r(2000), 2000)),
  new Promise(r => setTimeout(() => r(3000), 3000)),
  new Promise(r => setTimeout(() => r(4000), 4000)),
  Promise.reject(new Error("all Error"))
]).then(console.log);	// [{ status: 'fulfilled', value: 1000 }, ...] 형태로 반환

4. 마이크로 태스크 큐

프로미스의 후속 처리 메서드의 콜백 함수들이 저장되는 큐이다.
일반 태스크 큐에 저장되는 콜백 함수들 보다 우선순위가 높아서 먼저 호출된다.

setTimeout(() => console.log(1), 0);

Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));

// 2, 3, 1
// 프로미스가 우선순위가 높으므로 먼저 출력된다.

0개의 댓글