Javascript - promise 분석하기

ryan·2022년 5월 22일
0

참고자료 : [인간 JS 엔진 되기 - ZeroCho ]

비동기는 동시가 아니라 순서.

  • 비동기 코드도 정해진 순서가 있음
setTimeout(() => {
  console.log('a');
}, 0);

setTimeout(() => {
  console.log('b');
}, 0);

setTimeout(() => {
  console.log('c');
}, 0);

// a b c

이벤트 루프에서의 background

  • 본래 이벤트루프는 페이즈별로 세분화되어 있음.
  • background 부분은 web api, js 엔진, 운영체제를 포괄하는 개념을 편의를 위해 표현
  • background에는 프로미스, setTimeout, 네트워크 요청(ajax 요청 등), 이벤트 리스너, 커스텀 이벤트 등이 들어갈 수 있다.
  • background에서 실행된 함수는 task queue(macro, mirco queue)에 쌓이게 된다. queue에 쌓인 함수는 FIFO로 실행됨.

Queue의 종류

  • Micro Queue의 : promise, process.nextTick
  • Macro Queue의 : 나머지(setTimeout, eventListener 등)
  • 큐에 쌓인 함수가 동시에 실행되어야 하는 특정 상황일 때 micro queue의 함수가
    먼저 실행된다.
  • 계속해서 마이크로 태스트큐가 쌓이게 되면 매크로 태스트는 계속 실행이 안 됨

Promise

  • 프로미스를 쉽게 정의하면 '실행됐는데 결과값을 나중에 쓸 수 있는 것'이다.
  • 콜백헬이 안 좋은 이유는 가독성이 떨어지는 것도 문제지만 결괏값을 바로 받아서 사용해야만 한다.

일반적인 비동기 콜백과 프로미스의 차이

일반적인 비동기 콜백

일반적인 비동기 콜백
setTimeout(() => {
  console.log('a');
}, 1000);
  • 1000ms 뒤에 무조건 console.log('a')가 실행된다.

promise

promise
const a = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});
const b = a.then((res) => {
  console.log(res);
});
b.then(() => {});
  • 실행을 먼저 시키고 결괏값을 저장했다가 원할 때 꺼내서 쓸 수 있다.

promise all과 allSetteled의 차이

promise all

  • 하나라도 문제가 발생하면 catch로 이동함. 어디서 실패했는지 알 수 없음.
const p1 = axios.get('url');
const p2 = axios.get('url');
const p3 = axios.get('url');
const p4 = axios.get('url');
const p5 = axios.get('url');
const p6 = axios.get('url');

Promise.all([p1, p2, p3, p4, p5, p6])
  .then((results) => {})
  .catch((error) => {}); // 여기서의 catch는 promise.all과 then까지 포함된 catch다.

promise allSetteled

  • 성공과 실패 모두 then에서 구분하여 받을 수 있다.
Promise.allSettled([p1, p2, p3, p4, p5, p6])
  .then((result) => {})
  .catch((error) => {})
  .finally(() => {});

이벤트루프에서 promise 분석

  • new Promise를 사용하는 순간 promise의 내부 함수는 바로 호출되며, 내부 코드가 실행된다.
let a = 2;
const p = new Promise((resolve, reject) => {   // new Promise의 익명함수가 바로 호출된다.
  console.log('good'); // promise 내부의 동기 코드
  setTimeout(() => { // 비동기 코드, 호출 스택이 비워졌을 때 실행
    a = 5;
    console.log(a); 
    resolve(a);
  }, 0);
});
console.log('hi');
p.then((result) => {
  // then은 이벤트 리스너처럼 생각하는게 편하다. 이벤트 루프 백그라운드로 이동
  // then이 호출 스택에 불리기 위한 조건은 resolve가 호출되는 것. 그 전까지는 web api에서 대기
  console.log('result', result);
});
// result
// good hi 5 result,5

async, await 이벤트 루프

  • ansyc 호출 스택을 분석할 때는 async 내부의 첫 번째 await을 만나면 a는 호출 스택에서 실행된 것으로 생각해야 분석이 편하다.
async function a() {
  console.log('good')
  const a = await 1; // await > then으로 치환해서 생각하면 편함
  console.log('a', a);
  console.log('hi');
  await null;
  const b = await Promise.resolve(1);
  console.log('b',b);
}
// good 
// a 1
// hi
// b 1

promise로 변환해서 생각해보기
console.log('good')
Promise.resolve(1)
  .then((a) => {
    console.log('a', a);
    console.log('hi');
    return null;
  })
  .then(() => {
    return Promise.resolve(1);
  })
  .then((b) => {
    console.log('b',b);
  });

profile
프론트엔드 개발자

0개의 댓글