My Boundary As Much As I Experienced

JS) 비동기와 Promise 본문

FrontEnd/Javascript(Vanilla)

JS) 비동기와 Promise

Bumang 2023. 8. 1. 21:32

정의:

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다. Promise는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있습니다. 

 

말이 너무 어려운데, 쉽게 말하자면, 외부 API를 호출했는데 값이 아직 안 왔다는지, 의도적으로 setTimeout(시간 지연)하고나서 그 값을 활용해야된다는지 등등.. 다른 모종의 이유로 아직 값이 할당되지 않은 변수를 활용하는 방법 중 하나이다.

 

예를 들어 날씨 API를 활용하여 기상 예보를 보여주는 사이트가 있다고 해보자. 이 사이트에는 이런 로직이 있다.

1. 날씨 API를 사용해서 오늘의 날씨를 조회한다.

2. 그 결과값에 따라서 화창한 하늘 혹은 비오는 하늘을 메인페이지로 보여주는 사이트가 있다고 해보자.

 

2번은 1번의 연산이 끝나야지만 처리할 수 있는 프로세스이다. 그러나 이것을 분리된 코드로 작성하면 날씨 API를 조회해서 아직 불러오는 중인데(1번), JS가 벌써 다음 코드(2번)를 처리해버리고 "주인님, 날씨 값이 없습니다. 참조 에러에요." 혹은 "undefined입니다" 라는 둥 말해버린다..

 

그래서 지연 시간이 생기는 비동기 연산의 값을 가지고 다른 조건을 생성하거나 변수로 만들어 계속 활용하는 등 의존성이 있는 코드는 콜백함수 체이닝(콜백 지옥), promise나 async/await등의 처리를 하게 된다.

 

그러나 콜백 지옥은 이제 사용이 지양되므로 promise나 async/await을 하게 되는 것이다.

 

 

활용되는 상황:

1. API를 통한 데이터 통신의 결과를 변수에 지정해서 계속 활용하거나 해야될 때.

자바스크립트는 이벤트 기반 언어기 때문에 API통신에 의한 값이 변수에 제대로 지정되기 전에 벌써 다음 코드를 실행시킨다.

api를 통해 건네받은 데이터를 계속 체이닝해서 사용해야되는 경우, Promise를 사용한다.

 

2. setTimeout등 지연시간을 가지다가 처리해야될 때

API 통신의 경우가 아니더라도 의도적으로 지연시간을 가지다가 처리하거나, 어떤 이벤트가 일어난 시점에서 몇 초 뒤 실행시켜야되는 이벤트가 있을 수 있다. 이런 경우 setTimeout을 주로 사용하는데, 이를 통해 생성된 데이터도 계속 사용해야될 때, Promise를 사용한다.

 

 

 

 

기본적인 비동기 코드

setTimeout을 이용하여 원하는 시간이 흐른 뒤에 함수를 실행시킬 수 있다. 동기적 처리 흐름에서 벗어남.

console.log(1);
setTimeout(() => console.log(2), 1000); //대표적인 비동기 코드
console.log(3);
// 1
// 3
// 2

 

 

setTimeout을 동기적으로 쓰기 위한 전통적인 방법: 콜백지옥

n초 후에 특정 함수를 실행시키고, 또 그 함수가 실행된 이후 n초 후에 함수를 실행시키고... 이렇게 setTimeout을 연쇄시키고 싶을 때가 있다. 이럴 땐 함수에 setTimeout을 넣어 순차적으로 넣어 맨 마지막 a(()=>b(()=> c(()=> d()))) 패턴처럼 사용해야 된다(됐었다).

 

그러나 이런 사용방식은 코드 가독성이 최악이고 리팩토링하기도 힘든 상황이 만들어지기 때문에 콜백 지옥이라고 부른다.

(참고사항: setTimeout을 전역에서 쓰거나, 변수 지정만 해도 스케줄링 작동해버리기 때문에함수 스코프에 잘 넣어 사용하자...)

//setTimeout을 탑재한 함수들
const a = (callback) => {
  setTimeout(() => {
    console.log("aa");
    callback();
  }, 1000);
};
const b = (callback) => {
  setTimeout(() => {
    console.log("bb");
    callback();
  }, 1000);
};
const c = (callback) => {
  setTimeout(() => {
    console.log("cc");
    callback();
  }, 1000);
};
const d = () => {
  setTimeout(() => {
    console.log("dd");
  }, 1000);
};

// 콜백 지옥
a(() => {
  b(() => {
    c(() => {
      d();
    });
  });
});

 

 

 

 

콜백 지옥을 해결하기 위한 Promise 패턴

const a = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1);
      resolve();
    }, 1000);
  });
};

const b = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(2);
      resolve();
    }, 1000);
  });
};

const c = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(3);
      resolve();
    }, 1000);
  });
};

const d = () => setTimeout(() => console.log(4), 1000);

a()
  .then(b)
  .then(c)
  .then(d);

Promise를 반환하는 함수들을 만들어서 setTimeout을 쓰면, 들여쓰기 지옥인 콜백지옥을 회피할 수 있다.

(또 참고로 실험결과 함수안에 Promise를 반환하는 형태가 아니라 변수에 바로 Promise를 지정하고 그 안에 setTimeout을 넣는다면 스케줄링이 전역으로 잡힌다... 순서대로 값이 나오지 않고 동시다발적으로 호출 후 값이 나온다.)

 

promise는 resolve와 reject 2개의 인자를 가지고 있는데, 비동기 호출이 성공하면 resolve를 호출할 수 있다.

이때 resolve에 해당하는 콜백함수는 .then()으로 제공할 수 있다.

 

 

a()에 직접 제공하는 것이 아닌 then에 전달한다는 것을 유의. (이런 패턴을 처음 봐서 너무 낯설긴 하다.) 

a() 안에 직접 인자로 제공할 수 없나 실험해본 결과, 제공할 수 없다.

function a()안에 인자로 resolve에 해당하는 함수를 넣어줘도 new Promise(resove) 안에 들어가지 않는다.

 

 

반대로 비동기 함수 호출이 실패했을 땐 resolve가 아니라 reject 인자를 작동시킬 수 있다.

reject에 해당하는 함수를 체이닝 시키려면 .catch()를 써야한다.

a()
  .then(b)
  .catch(c)

then이 발동한 다음 잘 안 되면 catch가 발동하게 된다.

이외에도 비동기 패턴 체이닝을 시키는 방법으로 async/await이 있는데 이는 다음에 정리해보도록 함.

 

 

 

 

프로미스를 설정할 때 유의할 점:

const pros = promise((res, rej)=> console.log("pros"))

이렇게 프로미스를 함수로 안 감싸고 외부에 노출시키면 바로 호출 예약이 되어 버린다. 프로미스를 반환하는 함수 를 만들어서 사용할 것.

 

 

막간 상식) SetTimeout의 기본적인 원리

자바스크립트엔 콜스택메모리힙이 있다. 콜스택은 처리할 연산들이 stack구조로 쌓여서 처리되는 곳이며, 메모리 힙은 함수나 변수 선언들이 쌓이는 곳이다. 우리가 선언한 함수나 변수들을 가지고 여러 명령을 하면 콜스택에 쌓였다가 해소되는 것이다. 그런데 Timeout은 사실 web API이다. 이는 콜백큐(callback queue)에 쌓인다. 콜백 큐에 대기하고 있던 Timeout연산들은 이벤트 루프에 의해 콜스택이 빌때 넣어준다.

 

 

 

 

 

 

 

참고 자료:

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

'FrontEnd > Javascript(Vanilla)' 카테고리의 다른 글

JS) GSAP 자바스크립트 애니메이션 라이브러리  (0) 2023.08.02
append vs appendChild 그 차이점  (0) 2023.08.02
콜백함수  (0) 2023.08.01
JS) 프로토타입(Prototype)  (0) 2023.07.22
JS) this란 무엇인가?  (0) 2023.07.21