[Javascript] 비동기 함수 깔끔하게 처리하기

Gyuhan Park·2023년 9월 9일
3

javascript deepdive

목록 보기
10/11

💭 TMI

이벤트 루프 발표를 해야되는데 동기/비동기 개념이 헷갈려서 정리를 해보려고 한다. 나는 제대로 서버에 데이터를 요청했는데 결과값이 제대로 안나온다? 그럼 서버문제거나 동기 비동기를 제대로 이해하지 못해서이다.(물론 이유는 많겠지만) 서버를 탓하기 전에 무지한 나를 먼저 탓하며 비동기를 이해해보자

👏 동기(sync) vs 비동기(async)

동기 방식은 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다. A작업이 완료될 때까지 B 작업은 대기하므로 순서대로 처리된다.

비동기 방식은 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있다. A작업이 끝나지 않아도 B작업을 실행한다. 순서가 보장되지 않는다.

비동기 처리를 한 결과값이 다음 함수 실행에 필요한 경우 어떻게 하죠?

📞 콜백함수

비동기 처리가 완료된 후 콜백함수를 호출하면 된다.

JS에서는 함수가 일급객체이기 때문에 함수의 인수로 함수를 넘겨줄 수 있는데, 인수로 넘겨준 함수를 콜백함수라고 한다.

콜백함수로 비동기 처리하는 예를 들어보자.

function functionA(a, callbackA) {
  setTimeout(() => {
    const res_a = a + 5;
    callbackA(res_a);
  }, 1000);
}

function functionB(b, callbackB) {
  setTimeout(() => {
    const res_b = b * 5;
    callbackB(res_b);
  }, 1000);
}

functionA(1, (res_a) => {
  functionB(res_a, (res_b) => {
    console.log('res_b console:', res_b);
  });
});
  1. functionA 를 호출한다.
  2. 1초 뒤 functionA에서 가공한 res_a로 callbackA 를 호출한다.
  3. functionB 는 functionA에서 callbackA로 넘겨준 res_a를 받아 호출한다.
  4. 1초 뒤 functionB에서 가공한 res_b로 callbackB 를 호출한다.
  5. console.log 에는 (1 + 5) * 5 = 30 이 출력된다.

🔥 콜백지옥

위의 예시를 통해 콜백함수를 이용하여 비동기적으로 값을 반환하는 함수를 처리할 수 있게 되었다.
근데 만약 콜백함수가 많아질 경우 코드의 deps가 깊어지면서 이해하기 어려워진다.

다음과 같이 비동기 처리를 위해 콜백함수를 연속적으로 사용하여 코드가 깊어지는 문제를 콜백지옥이라고 한다.

functionA(1, (res_a) => {
  functionB(res_a, (res_b) => {
     functionC(res_b, (res_c) => {
      	functionD(res_c, (res_d) => {
			...    
  		});
  	}); 
  });
});

그럼 이런 콜백지옥을 어떻게 해결할 수 있을까?

🤝 Promise

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

-> 비동기 연산이 완료된 이후에 결과를 알기 위해 사용하는 객체

비동기 메서드를 마치 동기 메서드처럼 값을 반환
최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 프로미스를 반환

Promise는 3가지 상태를 갖는다.

  • 대기(pending) : 이행하지도, 거부하지도 않은 초기 상태
  • 이행(fulfilled) : 응답 성공
  • 거부(rejected) : 응답 실패

Promise.prototype.then()Promise.prototype.catch() 의 메서드의 반환 값은 새로운 프로미스를 반환하기 때문에 메서드 체이닝 가능

Promise 를 사용하면 비동기 작업의 개수가 많아져도 then()을 이용하여 계속 코드의 깊이가 깊어지지 않게 된다.

function functionA(a) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res_a = a + 5;
      resolve(res_a);
    }, 1000);
  });
}

function functionB(b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res_b = b * 5;
      resolve(res_b);
    }, 1000);
  });
}

functionA(1)
  .then((res_a) => {
    return functionB(res_a);
  })
  .then((res_b) => {
    console.log('res_b console:', res_b);
  });

📌 Promise의 단점

하지만 Promise를 사용해도 몇 가지 불편한 점이 있다.

  • 에러를 잡을 때 몇번째에서 발생했는지 알아내기도 어려움
  • 특정 조건에 따라 분기를 나누는 작업 어려움
  • 특정 값을 공유해가면서 작업을 처리하기 어려움

살짝 불편하긴한데 그냥 Promise로 만족해야 하나?

🥇 async/await

The async function declaration creates a binding of a new async function to a given name. The await keyword is permitted within the function body, enabling asynchronous, promise-based behavior to be written in a cleaner style and avoiding the need to explicitly configure promise chains.

-> async/await 을 사용하면 명시적으로 promise chain을 작성해야 하는 필요를 없애고 비동기 처리를 깔끔하게 처리할 수 있음

async : AsyncFunction객체를 반환하는 하나의 비동기 함수 정의
await : async 함수의 실행을 일시중지하고 전달된 Promise 처리가 끝난 후 값을 반환

  • 암시적으로 Promise를 반환
  • 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수
  • Promise 처리를 기다리는 동안 이벤트 루프에 의해 엔진이 다른 일(다른 스크립트 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.
  • await 키워드는 async 함수에서만 유효

다음 2개의 함수는 같은 값을 반환하는 걸 볼 수 있다.

async function foo() {
  return 1;
}

function bar() {
  return Promise.resolve(1);
}

console.log('foo():', foo());
console.log('bar():', bar());

Promise chain 으로 연결했던 함수들을 async/await 을 사용하여 개선해보았다.

async function functionA(a) {
  console.log('functionA start:', a);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res_a = a + 5;
      resolve(res_a);
    }, 1000);
  });
}

async function functionB(b) {
  console.log('functionB start:', b);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res_b = b * 5;
      console.log('functionB Promise:', res_b);
      resolve(res_b);
    }, 1000);
  });
}

async function solution() {
  const res_a = await functionA(1);
  console.log('solution res_a:', res_a);
  const res_b = await functionB(res_a);
  console.log('solution res_b:', res_b);
}

solution();

공식문서에 다음과 같은 문장이 있다.

await 표현식은 Promise를 반환하는 함수가 마치 동기적으로 동작하는 것처럼 처리
반환된 Promise가 완료될 때까지 함수의 실행 일시 중단

첫번째 await 문을 포함하는 최상위 코드는 동기적으로 실행됩니다.
따라서 await 문이 없는 async 함수는 동기적으로 실행됩니다.
하지만 await 문이 있다면 async 함수는 항상 비동기적으로 완료됩니다.

그냥 읽었을 땐 "엥 동기 비동기가 뭔가 반댄데?" 라고 생각해서 다음과 같은 코드를 예시로 작성해보았다.

async function functionA(a) {
  console.log('functionA start:', a);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const res_a = a + 5;
      resolve(res_a);
    }, 1000);
  });
}

async function solution2() {
  const notWait = functionA(1);
  console.log('not await:', notWait);
  const wait = await functionA(1);
  console.log('wait:', wait);
  const notWait2 = functionA(notWait);
  console.log('notWait2:', notWait2);
  const wait2 = await functionA(wait);
  console.log('wait2:', wait2);
}

solution2();

"await 문을 포함하는 최상위 코드" solution2() 는 동기적으로 실행된다.
"await 문이 없는 async function" functionA(1) 는 동기적으로 처리되어 Promise { <peinding> } 을 반환한다.
"await 문이 있는 async function" 인 await functionA(1) 는 비동기적으로 처리되어 수행이 완료된 1초 후 6 을 출력한다.
const notWait2 = functionA(notWait); 가 실행될 때 Promise { 6 } 이 찍힌 이유는 notWait에 await이 안붙었지만 1초가 지나(Promise 수행 완료) 6을 정상적으로 반환하는 것을 확인할 수 있다.

최상위 코드 solution2() 의 관점에서 const wait = await functionA(1); 문 뒤에 console.log('wait:', wait); 이 실행되어야 되는데 functionA 함수가 실행 완료될 때까지 기다리니까 비동기 처리라고 표현한다고 이해했다.

자바스크립트 공식문서
modern javascript

profile
단단한 프론트엔드 개발자가 되고 싶은

2개의 댓글

comment-user-thumbnail
2023년 9월 9일

글이 정말 깔끔해서 동기/비동기를 더 깊게 이해할 수 있었어요~
특히 await 문이 없는 async 함수는 동기적으로, await 문이 있는 async 함수는 비동기적으로 완료된다는 내용을 코드로 잘 풀어서 설명하셨네요!
좋은 글 감사합니다 ㅎ

답글 달기
comment-user-thumbnail
2023년 9월 10일

안녕하세요! gyu님의 블로그 잘 읽어보았습니다!

저는 uxui디자이너 취업준비생입니다!
다름이 아니라 it커뮤니티 앱 서비스를 제작하고싶어
설문조사를 준비했는데 참여 가능할까요!?

설문 시간은 약 7분 이내 입니다.

✅설문 완료자 분들 중 추첨을 통해 소정의 커피 기프티콘을 총 10분께 드릴 계획입니다.

  • 설문 기간 : 9월 07일 (목) ~ 9월 25일 (월)

✔설문링크✔
https://f9t6fmjryg6.typeform.com/to/fubJmSvJ

해주시면 감사하겠습니다 ㅠ̆̈ㅠ̆̈

답글 달기