본문 바로가기
JavaScript

JavaScript, 콜백함수와 Promise

by nacjji 2022. 11. 25.
콜백 함수를 이용한 동기/비동기적 처리

동기적 처리란 일반적으로 위에서 아래로 실행되는 코드를 처리하는 것을 말한다. 

하나의 작업을 끝내야 다음 작업을 실행할 수 있는 방식이다. 

직관적이지만 작업이 오랜 시간이 걸리는 작업이 끝나지 않으면 뒤에서 실행할 작업이 아무리 간단한 작업이라도 소요시간이 오래 걸릴 수밖에 없다. 

 

비동기적  처리란, 작업을 지시하고 작업이 진행되는 동안 다른 작업을 시작할 수 있다. 

따라서 동기적 처리보다 복잡하지만 효율적이다. 

 

- setTimeout 메소드를 통해 작성한 비동기적 처리를 보자.

console.log("Nice to meet you");

setTimeout(() => {
  console.log("Timer is done");
  fetchData((text) => {
    console.log(text);
  });
}, 2000);

console.log("Hello");
console.log("Hi");
→ Nice to meet you
    Hello
    Hi
// 약 2초 뒤
    Timer is done

- 위 코드를 실행해 보면 위에서 동기적 코드로 작성된 "Nice to meet you" , "Hello", "Hi" 가 먼저 출력되고, 약 2초(2000ms) 뒤 "Timer is done" 이 출력되는 것을 볼 수 있다. 

- 비동기적으로 setTimerout 메소드로 실행한 코드에게 작업을 지시하고 그 작업을 수행하는 동안 아래에 있는 "Hello", "Hi"코드를 실행한 것이다. 

- 비동기적 코드를 하나 더 추가한 예시를 보자. 

const fetchData = (callback) => {
  setTimeout(() => {
    callback("Done!");
  }, 1500);
};

console.log("Nice to meet you");

setTimeout(() => {
  console.log("Timer is done");
  fetchData((text) => {
    console.log(text);
  });
}, 2000);

console.log("Hello");
console.log("Hi");
→ Nice to meet you
    Hello
    Hi
// 약 2초 뒤
    Timer is done
// 약 1.5초 뒤
    Done

- fetchData 라는 함수의 매개변수로 약 1.5초 뒤에 "Done" 을 출력하는  setTimeout 함수를 넣었다. 

- 이 콜백함수는 호출이 되지 않았기 때문에 아직 "Done" 을 출력하지 않는다. 

- 두번째 setTimeout 함수 단락에서  "Timer is done" 을 출력하고 fetchData 함수를 호출하는데, 이 때 매개변수로 입력한 text 를 출력한다. 

- text는 첫 번 째 함수 단락의 매개변수에는 callback의 매개변수인 callback("Done!") 이 들어있기 때문에 1.5초(1500ms) 후에 Done을 출력한다. 

 

-하지만 이런 방식은 콜백함수를 계속해서 여러 번 중첩해서 사용하면 코드가 점점 깊어지고 난해한 콜백지옥에 갇히게 된다. 

- 이러한 문제점을 바탕으로 Promise 라는 개념이 등장하게 되었다. 

 

Promise

Promise 는 콜백함수를 보다 직관적으로 나타내기 위해 등장한 객체이다. 

- Promise 객체의 기본 형태

const 변수 = new Promise(executor)

- Promise 가 만들어 질 때 executor 를 인자로 가지며, executor 에는 함수만 올 수 있다. 

- executor의 인자로는 resolve, reject가 주입된다.

- executor는 Promise의 실행함수로, Promise가 만들어질 때 자동으로 생성된다. 

Promise는 3가지 상태가 진행되며 실행이 된다. 

  • 대기(Pending) : 이행되거나 거부되지 않은 초기 상태
  • 이행(Fulfilled) : 연산이 성공적으로 완료된 상태
  • 거부(Rejected) : 연산이 실패한 상태

Promise 를 사용하기 위해 먼저 new 생성자를 이용해 Promise 를 생성한다. 

const fetchData = () => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Done!");
    }, 1500);
  });
  return promise;
};

- 두 번째 줄에서 promise 상수에 Promise 를 생성자로 가져와 매개변수로는 resolve 와 reject 함수를 콜백하게 되는데 resolve 는 Promise 객체의 연산이 성공적으로 완료(Fulfilled)되었을 때 실행하는 함수이고, reject는 연산에 실패(Reject)되었을 때 실행하는 함수이다. 

- 이 fetchData 함수는 Promise 1.5 초 뒤에 "Done" 을 출력하는 setTimeout 메소드를 리턴하게 된다. 

const fetchData = () => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Done!");
    }, 1500);
  });
  return promise;
};

console.log("Nice to meet you");

setTimeout(() => {
  console.log("Timer is done");
  fetchData()
    .then((text) => {
      console.log(text);
      return fetchData();
    })
    .then((text2) => {
      console.log(text2);
    });
}, 2000);

console.log("Hello");
console.log("Hi");
→ Nice to meet you
    Hello
    Hi
// 2초 뒤 
   Timer is done
// 1.5초 뒤 text
     Done!
// 1.5ch 뒤 text2
    Done!

- Promise 가 만들어질 때 executor 가 실행되는데,  executor 의 resolve("Done!") 함수는 두 번 째 함수 단락에서

fetchData().then (...) 가 실행 되어야지 fulfilled 상태가 되며 실행되게 된다. 

 

- 독립적인 블록에서 then 을 사용해 비동기적으로 콜백함수를 호출하고 있어 콜백함수만을 사용한 것보다 직관적이고 간결한 것을 볼 수 있다. 

- 하지만 여전히 중첩이 여러번 될 경우 코드가 복잡해 질 수 있기 때문에 ES7 부터는 async, await 구문이 등장하게 되는데 이는 따로 작성하겠다.