[TIL] 24/04/30 Promise와 fetch

개념 공부를 할 때 가장 헷갈리는 비동기

여태 눈치껏 사용하다가 제대로 사용해 보고 싶어서 시작한다,, 가보자고,,🔥


Promise

이해하고 보니 명쾌한 정의!

비동기 작업을 처리하는 객체, 
비동기 작업을 실행했지만, 결과를 반환하지 않은 객체 ( 왜냐? resolve, reject를 호출하지 않으면 영원히 pending 상태)

 

const promise = new Promise((resolve, reject) => {
   if (작업이 성공적으로 완료됨) {
     resolve(결과값); // 성공적으로 완료되었으므로 resolve 호출
   } else {
     reject(에러); // 작업이 실패했으므로 reject 호출
   }
});

//딴짓
//딴짓중.. 
//계속 다른 작업중.. 

promise
    .then((결과값)=>{...}) // resolve 시 실행
    .catch((에러) => {...}) // reject 시 실행

 

Promise는 생성자로 생성 후, 비동기 작업 후 명시적으로 resolve / reject 호출이 필요하다

promise 라는 변수에 비동기 작업의 결과를 담아뒀다가, 후속 처리 메소드로 결과를 사용하면 된다! (하지만 catch는 에러 처리를 위해 분리를 잘 안한다고 한다)

 

state

Promise의 비동기 처리가 어떻게 진행되고 있는지를 나타내는 상태 정보이다

state는 resolve, reject의 호출로 결정된다

 

fulfilled, rejected 상태를 settled 상태라고 하는데, 비동기 처리가 수행된 상태를 의미한다

settled 상태가 되면, 더 이상 다른 상태로 변화할 수 없다 (고정!)

 

1. pending : promise가 생성되고, 호출될 때까지 계속 pending 상태 (= 비동기 처리가 아직 수행되지 않은 상태) 

2. fulfilled : 비동기 처리가 수행된 상태(성공) 

3. rejected : 비동기 처리가 수행된 상태(실패)

 

후속 처리 메소드

Promise가 resolve, reject를 통해 state가 변경되면 후속 처리를 해줘야 한다

state가 변경되면, 후속 처리 메소드에 전달한 콜백 함수가 실행된다 (이때 promise가 비동기 처리 결과를 인자로 전달한다)

1. then

fulfilled 시 호출되는 콜백 함수 

2. catch

rejected 시 호출되는 콜백 함수 + then에서 실패해도 catch로 처리

3. finally

상태 상관없이 무조건 한번 호출되는 콜백 함수 (공통 처리 있을 때 사용)

 

해당 메소드는 promise를 반환하기 때문에 promise 메소드를 계속해서 호출할 수 있는 "프로미스 체이닝" 가능하다

만약 promise가 아닌 값을 반환하더라도, 암묵적으로 resolve, reject를 통해 promise를 생성해서 반환한다

 

프로미스 체이닝을 이용해 여러 개의 비동기 처리를 순차적으로 실행할 수 있다

function asyncTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { // 1초 뒤 resolve
      resolve(1); 
    }, 1000);
  });
}

asyncTask()
  .then((result) => { 
    console.log(result); // 1
    return result * 2;
  })
  .then((result) => {
    console.log(result); // 2
    return result * 2;
  })
  .then((result) => {
    console.log(result); // 4
  });

 

Promise 정적 메소드

promise 상태가 변하면 후속 처리를 해야 한다 

Promise 정적 메소드 또한 promise를 반환하기 때문에 체이닝이 가능하다

1. resolve / reject

인수로 전달받은 값을 래핑하여 resolve / reject 하는 프로미스 반환

 

2. all

여러 개의 비동기 처리를 병렬로 처리한다

전달받은 promise가 모두 fulfilled 상태가 되면 모든 처리 결과를 배열에 담아 새로운 프로미스를 반환한다

하지만!

하나의 promise가 rejected 상태가 되면 나머지 promise를 기다리지 않고 catch로 즉시 종료된다

또한 만약 비동기 처리의 시간 차가 오래 걸린다면, 가장 긴 시간만큼 기다려야 한다

function asyncTask1() {
  return new Promise((resolve, reject) => { 
    setTimeout(() => { // 1초
      resolve(1);
    }, 1000);
  });
}

function asyncTask2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { // 2초
      resolve(2);
    }, 2000);
  });
}

function asyncTask3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { // 3초
      resolve(3);
    }, 3000);
  });
}

Promise.all([asyncTask1(), asyncTask2(), asyncTask3()])
  .then((results) => {
    console.log(results); // [1, 2, 3] //가장 오래 걸리는 시간 기준
    return results.reduce((acc, cur) => acc + cur, 0);
  })
  .then((sum) => {
    console.log(sum); // 출력: 6
  });

3. allSettled

전달받은 promise가 모두 settled 상태가 되면 각각의 promise의 결과를 {status: “” , value: }형태로 배열에 담아 새로운 프로미스를 반환한다

따라서 실패한 promise만 따로 추릴 수 있다!

function asyncTask1() {
  return new Promise((resolve, reject) => { 
    setTimeout(() => { // 1초
      resolve(1);
    }, 1000);
  });
}

function asyncTask2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { // 2초
      resolve(2);
    }, 2000);
  });
}

function asyncTask3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { // 3초
      resolve(3);
    }, 3000);
  });
}

Promise.allSettled([asyncTask1(), asyncTask2(), asyncTask3()])
  .then((results) => {
    console.log(results);
    /*
    [
      { status: 'fulfilled', value: 1 },
      { status: 'rejected', reason: 'Error in asyncTask2' },
      { status: 'fulfilled', value: 3 }
    ]
    */
  })
  .catch((error) => {
    console.error(error);
  });

4. race

가장 먼저 fulfilled 상태가 되는 promise 반환한다 

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, 'two');
});

Promise.race([promise1, promise2])
  .then((value) => {
    console.log(value); // "one"
  });

 

마이크로태스크 큐(Microtask Queue)

자바스크립트는 싱글 스레드로 비동기 처리는 WebAPI에서 처리한다

이때 프로미스 후속 처리 콜백함수는 콜백 함수가 저장되는 태스크 큐에 저장되지 않는다!!!

 

태스크 큐보다 우선순위가 높은 마이크로태스크 큐에 저장이 된다

순서

1. 동기 코드 실행

2. 프로미스 후속 처리 메소드 실행

3. 비동기 콜백 실행

console.log('Start'); // 1

// 태스크 큐에 작업 추가
setTimeout(() => {
  console.log('Task Queue - setTimeout'); // 4
}, 0);

// 마이크로태스크 큐에 작업 추가
Promise.resolve().then(() => {
  console.log('Microtask Queue - Promise resolve'); //3
});

console.log('End'); // 2

 

fetch

원격 API를 간단하게 호출할 수 있는 fetch 함수는 Promise를 반환한다

API 호출이 성공했을 때는 응답 객체(response)를 resolve하고, 실패했을 경우 예외 객체(error)를 reject 한다

 

fetch를 사용할 때 쓰는 국룰 코드

대부분의 REST API들은 JSON 형태의 데이터를 응답하기 때문에 보통 json()으로 변환 후 사용한다

fetch('https://jsonplaceholder.typicode.com/todos/1') // promise 반환
  .then(response => response.json())
  .then(json => console.log(json))

 

결론

1. Promise는 비동기 작업을 실행했지만, 결괏값을 반환하지 않은 객체로, 후속 처리 메소드로 원하는 시점에 결괏값을 가져올 수 있다

2. Promise에 전달하는 콜백 함수는 동기로 실행된다

3. 마이크로태스크 큐의 우선순위는 태스크 큐보다 높다


추가 공부할 내용

1. promise <-> async, await 변경 공부

2. axios로 적용하기

3. async/await 포스팅

 

참고 자료

제로초 인간js엔진되기

https://www.youtube.com/watch?v=NEaDPHNflGI&list=PLcqDmjxt30Rt9wmSlw1u6sBYr-aZmpNB3&index=10

 

드림코딩 async, await / promise

https://www.youtube.com/watch?v=aoQSOZfz3vQ&t=103