관리 메뉴

흰둥씨의 개발장

[javascript] Promise, async/await 가 무엇이냐! 본문

[오늘의 공부]/Javascript

[javascript] Promise, async/await 가 무엇이냐!

돈워리비해삐 2024. 6. 7. 03:49

자바스크립트는 싱글 스레드(=실행 창구가 하나다)이다. 그러니까 실행은 순서대로 되겠찌? 
슬프게도 Nope...

그러나 웹 브라우저 (=자바스크립트 실행기) 작동방식에 의해
web APIs를 통한 비동기 실행이 가능하다.  

비동기실행이란? 순차실행하다가 오래걸리는 연산을 만났다고 할 때,
기다렸다가 다음을 실행하는 것이 아니고, 일단 재껴두고 다음꺼 요청 넣음

웹 브라우저의 비동기 작업방식 짧게 요약 :
일단 오래 걸리는거 될때까지 webAPIs로 보내서 완료되면 큐->실행스택으로 올라오게 해서 실행하고,
오래걸리는거 저렇게 처리할동안 실행스택에서는 다음 코드를 실행요청 들어감
= webAPIs의 기능이 그런 편 (ex. setTimeout(), setInterval() <= 자바스크립트 문법이 아님)
= 더짧게 요약된걸 보고싶다면 아래 그려둔 사진을 참고해주세요
 
어떻게 동작하냐면!?

함수1();
오래걸리는연산();
함수2();

위와 같은 구문을 실행하면, 웹브라우저는 함수1()-> 오래걸리는연산()-> 함수2() 순으로 요청한다. 

다만, 오래걸리는 연산이 3초동안 실행되고 나서 값을 반환한다고 가정할 때, 
3초를 기다렸다가 함수2()를 실행하지 않음.
 
그래서 실행순서가 함수1()-> 오래걸리는연산() -> 함수2()의 순서로 보장되지 않는다. 

Heap에는 변수같은 내용이 저장되어있고, (메모리역할)
stack은 실행을 담당한다. (어쨌든 실행은 1개씩 = 싱글쓰레드)

stack에서 함수1()처리하고, 오래걸리는연산()을 만나면 바로 webAPIs로 잠깐 보내두고,
그동안 함수2()처리한다. 
그리고 webAPIs에서 다되었다 하면 Queue로 보내서, 기다리다가, 
스택이 비워지면 오래걸리는연산()이 스택으로 올라가서 처리된다. 

자바스크립트 개발자가 123의 순서로 실행해달라고 순서대로 적어놔도,
그 순서가 보장되지 않기 때문에,
"콜백함수"로 순서를 보장해왔다.
 

콜백함수란? 함수의 파라미터로 함수가 들어갈때, 그 파라미터안에 들어간 함수를 콜백함수라고 함 

document.addEventListener("click", ()=>{});
//addEventListener는 두번째 파라미터로 함수를 받는데,
//두번째 파라미터 함수가 바로 콜백함수인 것이다.

위와 같은 구문은 addEventListener()가 실행되고난후에, 그 안의 콜백함수가 실행된다. (=순서 보장되는중)
 
근데 문제가 생긴것은 콜백함수가 가독성이 좋지않다는 것이고, 
이거다음 저거, 저거다음 쩌거! 실행해달라고 하다보니 점점 콜백지옥이 되어간 것 

콜백지옥하면 유명한 예시그림

위 코드는 클린코드저자가 보면 뒷목잡을 코드...
 
아무튼 이런 가독성이 좋지 않은 콜백함수를 대체하기 위해 나온것이 Promise 이다. 
ES6에서 정식 채택되었다.

function fetchData(callback) { //fetchData는 콜백을 받아서 1초 뒤에 실행함
  setTimeout(() => {
    callback(null, '나는 데이터이다');
  }, 1000);
}

fetchData((error, data) => { //콜백함수는 첫번째 인자에 따라 데이터를 반환하거나 에러를 반환함
  error? console.error('에러 발생:', error): console.log('받은 데이터:', data);
});

위 함수는 1초뒤에 콜백함수를 실행하는 형태로 되어있다. 
이때 에러처리는 if문을 통해 주로 이뤄지는데, 많으면 많아질수록 깊이가 깊어져서 보기 힘들어짐

위 함수를 Promise를 사용해서 바꾸면? 
깊어지던 콜백지옥+중첩if의 늪을 then과 catch로 펴줄 수 있다. 

function fetchData() {
  return new Promise((res, rej) => { //promise를 반환하는 형태로 바꾸고, 
    setTimeout(() => {
      res('데이터');
    }, 1000);
  });
}

fetchData()            //함수실행시 
  .then((data) => {    //resolve객체가 오면 then을 실행하고,
    console.log('받은 데이터:', data);
  })
  .catch((error) => {  //reject객체가 오면 catch를 실행합니다.
    console.error('에러 발생:', error);
  });

위 함수는 1초 뒤에 성공인지 실패인지 알려주는 객체(promise)를 리턴하고,
promise가 성공상태이면then이 실행, reject상태이면 catch구문이 실행됨(에러처리가능)

Promise는 지금과 같이 비동기작업을 수행할 때, 사용한다. 
그러니까 이제 중간에 오래 걸리는 연산이 껴있을때,
순서를 보장해야 하는 경우
Promise를 이용하면 되는 것.
(주로 데이터 패칭할 때 많이 씀 = 활용해야하는 값이 시간좀걸려 응답될때
아니면 이터러블 프로그래밍 할때 왕왕 씀)

** 토막 상식(?) : Promise는 세 가지 상태를 가진 객체입니다. 

  1. 대기(pending): 초기 상태, 작업이 완료되지 않은 상태.
  2. 이행(fulfilled): 작업이 성공적으로 완료된 상태.
  3. 거부(rejected): 작업이 실패한 상태.

let promise = new Promise(function(resolve, reject) {
  // 비동기 작업을 수행합니다.
  if (/* 작업이 성공적이라면 */) {
    resolve("성공!");
  } else {
    reject("실패!");
  }
});

promise.then(function(result) {
  console.log(result); // 성공 시 결과를 처리합니다.
}).catch(function(error) {
  console.log(error); // 실패 시 에러를 처리합니다.
});




그런데 또 슬픈점 발생...
만약 여러개의 비동기처리가 걸려있는경우 then의 갯수가 늘어나게 되어있고,
그럴 땐 then/catch도 조금 가독성이 아쉽다.


그래서 promise를 쉽게 쓰기 위해,
에러처리 간편하게 하려고 (try/catch문으로 분리가능)
async/await가 나왔다. ES8 정식 채택.

function fetchData() {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res('나는 데이터이다');
    }, 1000);
  });
}

async function getData() { 
  try {
    const data = await fetchData();// await로 fetchData()의 결과를 받음
    console.log('받은 데이터:', data);
  } catch (error) {
    console.error('에러 발생:', error);
  }
}

getData();

1. function앞에 async를 사용하면 그 함수는 Promise를 반환한다. 
2. 그리고 Promise객체의 연산결과를 이용하고 싶다면 await키워드로 꺼내면 된다.
3. await는 promise가 해결될 때까지 잠시 기다리는 역할을 하고, resolve가 되면, 결과값을 리턴해 줌 

Promise를 이용할 때 then으로 결과를 받을수도 있지만,
await을 이용해서도 받을 수 있게 된다. (그래서 await는 thanable에만 쓸 수 있음)

await는 then의 역할을 한다. promise가 resolve나 reject될때까지 기다려서 응답을 리턴한다. 

async function example() {//promise를 반환하기 때문에 
  return "Hello, World!";
}

example().then(console.log); // then으로 결과를 볼수있음
async function fetchData() {
  let response = await fetch('https://api.example.com/data'); //서버응답 기다려서 결과받아주고,
  let data = await response.json(); //서버에서 받은 응답을 json으로 변환해서 
  console.log(data);                //보여주지요. 
}

fetchData();

 
response와 data를 처리하는데에 thenthenthen으로 늘어나지 않고,
async/await만 달아서 처리 가능해짐 = 보기 편해짐