Promise 이해하기

신호승·2021년 1월 11일
1

이 포스트는 순수하게 필자의 생각을 정리한 것이므로 잘못된 내용이 있을 수 있습니다. 피드백 환영합니다.

브라우저의 비동기 작업

  • 브라우저의 비동기 작업은 내장되어있는 Asynchronous Web APIs를 통해 진행됨
  • e.g. SPA를 가능하게 해 주는 XMLHttpRequest
  • e.g. 원활한 animation이 가능하게 해 주는 requestAnimationFrame
  • 비동기로 일어날 작업을 주로 콜백 함수를 통해 등록하도록 되어 있음

Promise 자체는 비동기가 아니다

  • Promise 패턴이 생겨난 가장 중요한 원인은 '콜백 지옥'을 해결하기 위해서임
  • 비동기 작업을 도와주는 synthetic sugar라고 볼 수 있음
  • 따라서 Promise 객체 자체는 비동기 / 동기 동작을 나누는 기준이 되지 않음
  • 다음과 같은 코드에서 A는 Promise 객체이지만 resolve 혹은 reject가 동기적으로 처리됨
function doSyncWork () {
    return !!Math.round(Math.random());
    // true, false를 랜덤으로 반환함
}

let A = new Promise((resolve, reject) => {
    let work = doSyncWork();
    if (work) resolve(work);
    else reject('invalid work');
})

console.log(A);
// 이미 fullfilled 혹은 reject 처리가 되어 있다 
  • resolve나 reject를 다른 비동기 API의 콜백 내부 에 집어넣어야 비동기로 동작함
  • e.g. resolve를 setTimeout 콜백 함수에 집어넣는 경우

예시: Axios에서 Promise를 어떻게 사용하고 있는가

  • Axios는 XMLHttpRequest를 Promise 객체로 감싸서 사용자에게 제공하고 있음
  • Axios 에 get / post 등 요청이 들어오면, 새로운 XMLHttpRequest 객체를 Promise 객체 내부에 생성함 => 관련 코드
  • XMLHttpRequest 객체의 onreadystatechange 메서드 콜백 함수 안에 resolve 및 reject 등록 => 관련 코드

주의: then / catch 핸들러는 비동기로 동작한다

function doSyncWork () {
    return !!Math.round(Math.random());
    // true, false를 랜덤으로 반환함
}

let A = new Promise((resolve, reject) => {
    let work = doSyncWork();
    if (work) resolve(work);
    else reject('invalid work');
})

A.then(() => console.log('async')).catch(() => console.log('async'));
console.log(A);

결과 : 
Promise <Fullfilled> or <rejected>
'async'

// then과 catch는 먼저 작성되었음에도 불구하고, console.log(A) 가 먼저 실행되었다
  • Promise의 Resolve와 Reject는 microtask queue에 담기기 때문에 콜 스택에 담긴 자바스크립트가 전부 실행되고 나서 바로 실행됨

왜 Microtask queue 에서 실행될까?

microtask 간 동일한 환경 보장

비동기의 더 빠른 처리

doReallyAsyncWork1() //프로미스를 리턴하는 비동기 api
  .then(result => syncWork1(result)) // 만약 SyncWork이 반환하는 것이 promise가 아니면,
  .then(result => syncWork2(result)) // then 안에서는 Promise.resolve()로 감싸서 
  .then(result => syncWork3(result)) // 강제로 promise resolve 처리를 해서 내보낸다. 
  .then(result => doReallyAsyncWork2()) 
  .catch(err => alert(err))
  • 위와 같은 코드에서, 비동기 동작을 요구하는것은 오직 2번 뿐인데, then 핸들러를 수행할때마다 똑같이 task queue에 담아놓으면 불필요하게 작업이 밀려 실행될 수 있음
  • Microtask queue에서 실행되면 task 보다 우선적으로 처리 가능하므로, syncWork1, syncWork2, syncWork3는 main thread가 microtask를 수행할 때 전부 실행되어, task queue에 있는 task보다 더 우선되어 처리됨
  • 따라서 더 빠른 처리가 가능함
profile
Front-End developer

0개의 댓글