programming language/javascript

[javascript] 동기 & 비동기 / promise, async, await 사용하기

공대키메라 2022. 5. 24. 23:07

필자는 최근에 회사에서 cals studio라는 node.js기반 프레임워크를 이용해서 AWS lambda 서버에 코드를 올리고 있다.

 

사실 그렇다는데 나는 뭐 그런가 보다 하고 이것을 내가 처음부터 직접 만든게 아니라 잘은 모른다.

 

하지만, 여러 비동기 처리 로직이 있는거 같은데 js에서 동기와 비동기를 어떻게 처리하고,

 

이러한 과정에서 콜백을 어떻게 잘 관리하는지 에 대해 학습하려고 한다. 

1. 동기(Synchronous)? 비동기(Asynchronous)?

동기와 비동기를 설명하려면 어떻게 해야 할까?

 

이를 알기 위해 빌드업을 하겠다.

 

Javascript는 싱글 스레드 언어로, 코드가 작성된 순서대로 작업을 처리한다. 

 

여러개의 작업이 있으면 앞의 작업이 끝날 때 까지 기다렷다가 뒤를 순서대로 실행하는 방식으로 동기적 방식이라고 한다. 또 그 와중에, 다른 작업이 불가능하도록 막는 것을 블로킹이라고 한다. 

 

근데 여러개의 작업을 동시에 언제 끝나든지 상관없이 실행하고자 한다면?

 

즉, 먼저 작성된 코드의 결과를 기다리지 않고 다음 코드를 바로 실행하는 것이 비동기 방식이다. 

 

그러면 다른 작업을 블로킹하는 동기 방식과는 달리 비동기 방식은 논 블로킹이라고 할 수 있나? 바로 그렇다.

 

한줄로 정리해보면...

 

동기는 직렬로! 비동기는 병렬로 처리가 일어난다고 생각하면 된다. 

동기적 방식 예

function firstFunc() {
  console.log("first");
}

function second(){
  console.log("second");
}

firstFunc();
second();

console.log("end");

 

정말 별 거 없는 코드로, 앞에서 말했듯이 우리가 선언한 함수가 순서대로, 즉 동기적으로 작동한다. 

비동기적 방식 예

function firstFunc(a,b, inFunc) {
  setTimeout(() => {
    const result = a +b;
    inFunc(result);
  }, 2000);
}

firstFunc(5,5, (res) => console.log(res));
console.log("end");

 

다음은 순서대로 작업이 실행되는 것이 아닌 비동기적인 방식으로 코드가 실행된다. 

 

먼저 "end"를 출력한 후에 2초 후에 익명 함수를 전달하는하는데, 이것이 inFuc로 들어가게 된다.

 

그러면 result를 inFunc안에 파라미터로 넣어주면 5+5인 10이 출력된다. 

 

다음 예시는 그럼 어떤 결과가 나올까?

function firstFunc(a,b, inFunc) {
  setTimeout(() => {
    const result = a +b;
    inFunc(result);
  }, 2000);
}
function secondFunc(a,inFunc) {
  setTimeout(() => {
    const result = a * 2;
    inFunc(result);
  }, 3000);
}

firstFunc(5,5, (ares) => {
  console.log("first : ", ares)
  secondFunc(ares, (second) => {
    console.log("second : ", second)
  })
});
console.log("end");

 

다음과 같은 결과를 반환한다. 

 

 

근데 뭔가 좀 쎄하다.

 

콜백을 이런 방식으로 계속 넘길 수 있는데 이렇게 계속 넘기면 코드를 이해하기도 힘들듯하다. 

 

출처 : https://hanamon.kr/javascript-%EC%BD%9C%EB%B0%B1-%EC%A7%80%EC%98%A5-%ED%83%88%EC%B6%9C%ED%95%98%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95/

 

콜백 지옥이라고 흔히 개발자들이 말하는데 이러한 문제를 해결해주는 것으로 Promise가 있다.

2. Promise

https://www.sedaily.com/NewsView/1VJ9N5TM6B

 

이것을 떠올린 분들... 프로미스 나인이 아닌 우리의 프로미스에 집중하자 (올ㅋ)

 

프로미스는 3개의 상태중 하나로 존재한다. 

 

  • 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

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

 

어떤 함수가 promise 객체를 반환한다는 말은 그 함수는 비동기적으로 동작을 하고 반환한 Promise객체를 이용해서 비동기 처리 결과를 then 과 catch로 이용할 수 있게 만들겠다는 의미다. 

 

function firstFunc(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = a + b;
      resolve(result);
    }, 2000);
  });
}
function secondFunc(a) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = a * 2;
      resolve(result);
    }, 2000);
  });
}

firstFunc(6, 7)
.then((first_res) => {
  console.log("first result : ", first_res);
  return secondFunc(first_res);
})
.then((second_res) => {
  console.log("second res : ", second_res);
});

// 이렇게 쓰는거 아님
// firstFunc(6, 7).then((first_res) => {
//   console.log("first result : ", first_res);
//   secondFunc(first_res).then((result) => {
//     console.log(result);
//   });
// });

 

출력 결과

 

내사랑 콜백... 나는 너가 올때까지 여기서 기다릴게... Promise... ㅠㅠ...

 

또, 중간에 then으로 chaining하지 않고 잘라서 사용도 가능하다.

 

function firstFunc(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = a + b;
      resolve(result);
    }, 2000);
  });
}
function secondFunc(a) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = a * 2;
      resolve(result);
    }, 2000);
  });
}

const fResult = firstFunc(6, 7)
.then((first_res) => {
  console.log("first result : ", first_res);
  return secondFunc(first_res);
});

console.log("testing...")
console.log("testing...")
console.log("testing...")

fResult.then((second_res) => {
  console.log("second res : ", second_res);
});

 

그런데 promise를 이렇게 직접 사용할 수 있지만, 우리는 좀 더 편리하게 Async, Await을 이용해서 비동기 코드를 우리 마음데로 조작할 수 있다.

3. Async, Await

Async를 붙인 함수는 반환값이 자동으로 Promise가 된다! 앞에서 우리가 보았듯이, new Promise할 필요 없이 알아서 담아준다는 말이다. 

 

async function testAsync() {
  return "test Async";
}

testAsync().then((res) => {
  console.log(res)

 

Promise에는 resolve와 reject부분이 있다고 했는데, testAsync에서 반환해주는 값인 "test Async"이 resolve의 값으로 전달되는것과 동일하다. 

 

이것을 좀 더 다채롭게 사용하자면 다음과 같다. 

 

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function testAsync() {
  return delay(3000).then(() => {
    return "test async";
  });
}

testAsync().then((res) => {
  console.log(res);
});

 

이것을 다시 다음과 같이 변경할 수 있다.

 

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function testAsync() {
    await delay(3000)
    return "test async";
}

async function final() {
    const result = await testAsync();
    console.log(result)
}

 final()

 

await keyword를 비동기 함수 앞에 붙이게 되면 비동기 함수가 마치 동기적인 함수처럼 작동하게 된다. 

 

이렇게 하면 promise를 사용하는 것 보다, 그리고 복잡하게 안에서 직접 then을 이용해서, chaining을 이용해서 사용하는 것 보다 편리하게 사용할 수 있다.

 

다른 예도 한번 실행해 보겠다.

 

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

function throwError() {
  return new Promise((resolve) => {
    throw new Error("rewsrwer");
  });
}

async function testAsync() {
  await delay(3000);
  return "test async";
}

async function final() {
  const result = await testAsync();
  console.log(result);
  return "result done!";
}

final().then((res) => {
  console.log(res);
  console.log("성공!");
});

 

이렇게 어떻게 사용하는지를 알아보았다.

 

이게 아 이해했네 햇다가도 사실 필자는 많이 사용한 경험이 없어서 뒤돌아스면 금붕어인지 이해가 안됐다.

 

매일 연습하면서 진짜 내것으로 만들도록 하겠다.

 

출처 

https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/

https://ko.javascript.info/async-await#ref-21

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8