[TIL #15] 동기, 비동기 란? (Promise, async & await)

JMinkyoung·2021년 8월 4일
0

TIL

목록 보기
15/42
post-thumbnail

동기 vs 비동기

JavaScript를 배우게 되면 가장 처음 만나는 중요한 개념중 하나가 바로 동기(Synchronous)비동기(Asynchronous)다!

동기 : 위의 그림과 같이 하나의 일이 끝나야 다음 일을 수행하는 방식을 의미한다.
비동기 : 다른 일이 끝나는 것과는 상관 없이 다음 일을 수행하는 방식을 의미한다.

JavaScript는 주어진 Event를 처리하는 Call stack이 하나뿐인 Single Thread이므로 만약 동기 방식을 사용한다면 Event가 여러개 들어오게 되면 한번에 한 Event씩 밖에 수행하지 못하므로 엄청난 시간을 기다려야 한다. 따라서 JavaScript는 비동기 방식을 따르고 있다!

비동기(Asynchronous) 예제

// 1번 Event
console.log("첫번째 이벤트");

//2번 Event
setTimeout(function() {
  console.log("두번째 이벤트");
}, 3000);

//3번 Event
console.log("세번째 이벤트");

동기 방식을 따르던 언어를 사용하다가 처음 JavaScript를 마주하게 되면 위의 코드를 실행 했을 때

  • 첫번째 이벤트
  • (3초뒤에) 두번째 이벤트
  • 세번째 이벤트

이 순서대로 출력될 것으로 생각하게 된다.

하지만 실제로 위의 코드를 입력하게 되면 결과는 다음과 같다.

  • 첫번째 이벤트
  • 세번째 이벤트
  • (3초뒤에) 두번째 이벤트

이러한 비동기 방식이 문제가 되는 경우중 하나는 1. Backend 서버로부터 어떠한 데이터를 요청하고 2. Front에서 해당 데이터를 받고 나서 데이터를 이용하는 코드를 비동기 흐름을 처리하지 않고 수행하게 된다면 서버로부터 데이터를 받기도 전에 데이터를 이용해버리는 말도 안되는 상황이 생기게 된다.

비동기 흐름 처리 방법

1. 콜백 지옥 (Callback Hell)

firstEvent(function(result1) {
  secondEvent(result1, function(result2) {
    thirdEvent(result2, function(finalresult) {
      console.log(finalresult);
    }, failureCallback);
  }, failureCallback);
},failureCallback);

함수 A 호출에서 함수 B가 인자로 전달될 때, 함수 B를 콜백 함수라고 부른다. 위의 예제의 경우에는 thirdEvent 함수를 호출 할 때 secondEvent를 인자로 전달하고, secondEvent함수를 호출 할때는 firstEvent를 인자로 전달하는 그야말로 지옥에 빠지게된다 👿

만약 코드가 더 길어지고 복잡해진다면 코드의 가독성은 떨어지고 나중에 수정할 일이 생기게 되면 어디서부터 어떻게 고쳐야할지 막막해지는 문제가 생긴다. 이러한 콜백 지옥 문제를 해결하기 위해 Promise가 등장하게 된다.


2. Promise


Promise는 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 한다. 다시말해 비동기 연산 종료 후에 해야할 행동들을 약속한다고 보면 된다.

Promise는 다음 중 하나의 상태를 가진다.

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

Promise 예제

1. Promise 생성

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(200);
  },  1000);
});

new Promise를 통해 Promise 객체를 생성하고 함수를 인자로 받으며 해당 함수는 resolve, reject 함수를 인자로 받게 된다.

  • resolve : 연산이 성공적으로 완료된 경우의 최종 결과를 반환
  • reject : 연산이 실패한 경우 Error object를 반환


2. Promise 사용

promise
  .then(resolve => console.log(resolve);
        
// 200이 출력된다.

resolve 되는 값, 즉 성공적으로 완료된 결과 값은 then 메소드의 인자로 넘어가게 된다.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(404);
  },  1000);
});

promise
  .then(resolve => console.log(resolve);
  .catch(error => console.error(error);

//error!가 출력된다.

reject 되는 값, 즉 연산이 실패하게 되면 catch 메소드의 인자로 넘어가게 되고 그 안에서 에러 핸들링이 가능하게 된다.


3. Promise Chaining

⚡순차적으로 각각의 작업이 이전 단계 비동기 작업이 성공하고 나서 그 결과값을 이용하여 다음 비동기 작업을 실행해야 하는 경우에 Chaining을 이용하여 해결한다. MDN

doSomething()
 .then(result => doSomethingElse(result))
 .then(newResult => doThirdThing(newResult))
 .then(finalResult => {
   console.log(`Got the final result: ${finalResult}`);
 })
 .catch(failureCallback);

위의 코드에서 doSomething의 반환 값은 then 함수의 인자로 전달되고, doSomethingElse, doThirdThing 반환 값 또한 then 함수의 인자로 전달된다. 여러개의 Promise 객체들 중 하나라도 에러가 발생하게 되면 catch로 넘어가게 된다.


4. Chaining after a catch

Chain에서 작업이 실패하여 catch로 넘어간 후에도 새로운 작업을 수행하는 것이 가능하다!

new Promise((resolve, reject) => {
    console.log('시작');
    resolve();
})
.then(() => {
    throw new Error('에러 발생!');
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('catch 이후 then으로 넘어감');
});

위와 같은 코드를 실행하게 되면 결과는 다음과 같다.

시작
Do that
catch 이후 then으로 넘어감

throw new Error('에러 발생!) 을 통해 에러가 생성되고 바로 catch로 넘어가서 'Do that' 을 출력하게 된다. 이후 에 다음 then으로 넘어가서 작업을 수행하게 되는 것이다.


3. async & await

async function 선언은 AsyncFunction 객체를 반환하는 하나의 비동기 함수를 정의한다. 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환한다. MDN

async & await (이 둘은 항상 짝으로 붙어다닌다)Promise를 대체하는 것이 아니라 Promise를 사용하면서 then, catch 메소드를 사용하여 컨트롤 하는 것이 아닌 동기적 코드처럼 반환 값을 변수에 할당하여 작성할 수 있게 해주는 방식이다.

async & await 예제

const resolveAfter2Seconds = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
};

const asyncCall = async () => {
  try{
    console.log('calling');
    const result = await resolveAfter2Seconds();
    console.log(result);
  }catch(error) {
    console.error(error);	// 에러 발생 시 catch 할 수 있도록 try/catch문으로 작성
  }
};

asyncCall();

위와 같이 Promiseasync & await으로 작성된 코드를 실행하게 되면 결과는 다음과 같다.

calling
resolved


동기, 비동기 방식이 가장 발휘되는 부분은 아무래도 서버쪽과 프론트 사이에서 데이터를 주고 받고, 요청을 주고받는 과정일 것 같다. 나 역시도 강의를 들으면서 해당 부분에서 동기, 비동기 처리의 중요성을 느꼈다. 이렇게 이론적으론 나에겐 아직 어려운 내용이지만 프로젝트를 진행하다보면 아마 몸으로 느끼지 않을까..😅

참고자료1
참고자료2
참고자료3

profile
Frontend Developer

0개의 댓글