[TIL] Call back, Promise, Async Await (비동기)

이재훈·2020년 9월 30일
0

What I leaned Today :)

    😎 Call back
    😎 Promise
    😎 Async Await

🌱 동기, 비동기란?

   😆 Potter's 영한 사전

        Synchronous : 동기
        Asynchronous : 비동기
        
        thread : 
	스레드는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다.
        일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만,
        프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다.
        이러한 실행 방식을 멀티스레드라고 한다
        
     

(위의 그림을 참고하면서 읽어보는 것을 추천합니다.)

동기(synchronous)

"동시에 일어난다."라는 개념으로, 요청과 그 결과가 동시에 일어난다는 뜻이다.
이 말만 보면 매우 빨라 보이는 프로세스처럼 보이지만,
다시 생각해보면 결과가 발생할 때까지 다음 절차들은 일시정지가 되는 것이다. -> blocking 발생

즉, 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져야 한다.

  • 요청과 결과가 한 자리에서 동시 발생

비동기(Asynchronous)

동기와 반대 개념으로, "동시에 일어나지 않는다."라는 정의를 가지고 있다.
즉, 요청과 결과(응답)가 동시에 일어나지 않는다는 말인데

나는 요청을 해놓고 다른 내 할 일을 하고 있으면 때가 되서 요청한 결과가 알아서 나온다는 것이므로,
요청을 한 후 응답이 올 때까지 내 할 일을 하고 있으면 되는 것이다.

  • 요청한 그 자리에서 결과가 주어지지 않음

  • 작업 처리 단위를 동시에 맞추지 않아도 된다.

    보통 요청을 하면 바로 결과가 나오지 않고, 결과(응답)가 나오기 전까지 시간차가 있기 때문에
    실무에서는 비동기를 많이 접한다고 한다.

    큰 용량의 파일을 로딩하는 등의 작업을 하기위해서는 반드시 비동기 처리가 필요해보인다.

[정리]

동기: 멀티태스킹 X, 요청이 있으면 응답이 올 때까지 아무런 작업 불가
비동기: 멀티태스킹 O, 요청을 한 후 다른 작업이 가능

쉽게 예를 들자면,
카페에서 커피 주문을 한다고 가정을 하자.

동기적인 상황
손님들이 줄을 서서 커피 주문을 하고 기다리는 상황에서, 바리스타는 주문을 받고 커피 추출하고 만들어진 커피를 제공할 때까지 뒤에서 기다리고 있는 사람들은 주문조차도 못하고 계속 주구장창 기다리게 된다.
손님1 주문 -> 손님 1의 커피 제조 -> 손님 1의 커피 제공 -> 손님 2 주문 -> 손님2의 커피 제조 -> 손님2 커피 제공 -> 손님 3 ....(요청,응답 1set로 처리)
손님들이 10명이 기다리고 있다면...?
매우 비효율적으로 장사를 하게 되는 잔치가 벌어진다.

반면, 비동기와 같은 상황
밀려있는 손님들을 응대하기 위해
주문 -> 다음 손님 주문 -> 그 다음 손님 주문 (요청) 그 주문을 받는 동안 한 쪽에서는 커피 제조, 손님은 자리에서 커피 나올 때까지 대기하면서 각자 할 일 함 (비동기) -> 진동벨, 커피 제공 (응답)

자바스크립트는 동기적으로 작동이 된다고 한다.
하지만 이러한 비효율성의 방지 혹은 상황에 따라 비동기적인 때가 필요해
비동기 함수의 중요성이 더 부각되어지고 있다.

대표적인 비동기 함수 : setTimeout()

console.log("Potter");

setTimeout(() => {
  console.log("awesome");
}, 2000);

console.log("is");

/*
Potter
is
(..2초뒤)
awesome  

순차적으로 처리하는 자바스크립트에 중간에 
setTimeout으로 이벤트 발생을 2초뒤로 걸어놓고 
그 다음 순서인 is를 처리한 후 awesome이 나타난다. 
즉, setTimeout이 2초 뒤 awesome으로 응답하는 과정 중간에 is라는 것을 출력하는 멀티테스킹 작업

*/
  

JavaScript 언어는 싱글스레드, 콜스텍의 특성을 가지고 있어,
순차적으로 연산이 되며, 먼저 실행된 연산이 끝난 후 다음 연산이 들어가는 구조를 가지고 있다. 이에 JavaScript에 있어서 비동기는 더욱 더 효율적이라고 볼 수 있다.
ex) 클라이언트(브라우저) 요청 -> 서버 응답 : 응답이 언제 올 지 모르는 경우, 이 때 자바스크립트는 응답이 오기 전 다른 연산 수행을 할 필요성이 있다.
이 때 비동기처리 함수인 Call back을 사용한다.

🌱 어떻게 JS언어에서 비동기 작업을 할 수 있을까?

Call back

싱글스레드와 동적인 특성을 가지고 있는 JavaScript를 비동기적으로 실행할 수 있게 해주는 기능을 가지고 있다.
또한, Call back은 "나중에 부를테니 그 때 와라"라고 해석이 되는 것처럼
보통 다른 함수의 인자로 들어가 특정 시기에 콜백이 실행이 되는 구조이다.
따라서 콜백을 이용해 연산 순서도 정의할 수 있을 것이다.

타이머를 이용해서 비동기로 이루어진 콜백을 실행해주는 걸 구현해보자
각 콜백함수를 각각 3초 뒤에 실행되게끔 설정해보았다.
(setTimeout()사용)

  const delay = (callback) => {
  	setTimeout (callback, 3000);
  };

  let arr = [];

  delay(function() {
    console.log('Potter');
    arr.push('Potter');
    delay(function() {
      console.log('is');
      arr.push('is');
      delay(function() {
        console.log('awesome');
        arr.push('awesome');
        delay(function() {
          console.log(arr);
        });
      });
    });
  });

// 3초를 간격으로 Potter -> is -> awesome 이 찍힐 것이고,
// 마지막에 arr라는 배열 안에 ["Potter", "is", "awesome"]이 찍힐 것이다.

동적이고 싱글스레드인 자바스크립트를 비동기적으로 실행해주는 편리함이 있지만
혹시 콜백 지옥이라고 들어봤는가.

const delay = (callback) => {
  setTimeout(callback, 1000);
};

let arr = [];

delay(function () {
  console.log("1");
  arr.push("1");
  delay(function () {
    console.log("2");
    arr.push("2");
    delay(function () {
      console.log("3");
      arr.push("3");
      delay(function () {
        console.log("4");
        arr.push("4");
        delay(function () {
          console.log("5");
          arr.push("5");
          delay(function () {
            console.log("6");
            arr.push("6");
            delay(function () {
              console.log("7");
              arr.push("7");
              delay(function () {
                console.log("8");
                arr.push("8");
                delay(function () {
                  console.log("9");
                  arr.push("9");
                  delay(function() {
                  console.log(arr);
                  });
                });
              });
            });
          });
        });
      });
    });
  });
});

아... 눈이 핑핑 도는 것만 같다.
위의 예시코드처럼 콜백처리해야할 것이 많아진다면 가독성이 현저히 떨어지는 것을 볼 수 있다.
이 콜백 지옥으로부터 벗어나기 위해 탄생한 것이 Promise라는데
다음에서 살펴보자.

Promise

Promise는 callback chain을 효율적으로 다뤄 위와 같은 콜백 지옥으로부터 탈출 할 수 있게 한다.

Promise는 마치 하나의 class와 같은 것이다.
callback 을 인자로 받지 않고, 새로운 promise 인스턴스를 바로 리턴한다.
(단, promise만의 callback을 인자로 받음 -> resolve, reject)

resolve() 실행이 promise로 되어있다면 -> .then (앞의 것이 끝나면 내 뒤의 것들을 실행시켜줘)
reject() 실행이 promise로 되어있다면 -> .catch로 에러 처리를 할 수 있다.

promise로 풀어보면 아래와 같다.

  function delayP(sec) {   // 실행에 필요한 옵션을 파라미터에 삽입
    return new Promise((resolve, reject) => {  // promise만의 callback
      setTimeout(() => {
        resolve(sec);		
      }, 1000);
    });
  } 
    
    delayP(1).then((result) => {  //1을 실행하고 그 다음에 그 결과를 찍어라
      console.log(result);
      return delayP(2);	// 그 다음에 .then으로 실행할 delayP의 연결고리
    }).then((result) => {  // 2를 리턴했으면 결과를 보여줘라.
      console.log(result);
      return delayP(3)
    }).then((result) => {
      console.log(result) // 3을 리턴했다면 결과를 보여줘라
    })


// 순차적으로 1-> 2-> 3 이 찍힐 것임

.then으로 체이닝을 해주는 덕분에 callback 보다 간결하고 가독성이 훨씬 나아졌다.
하.지.만.
위의 예시코드가 1에서 3으로 끝나는 것이 아니라.. 9까지 간다면..?
혹은 아래코드와 같이 여러개의 함수를 진행하고자 한다면?

  function jaehoon() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Jaehoon is studying JS')
      }, 1000);
    })
  }

function potter() {
  return new Promise((resolve, reject) => {
    setTimeout(() =>{
      resolve('Potter wants to sleep')
    }, 1000);
  });
}

function john() {
  return new Promise((resolve, reject) => {
    setTimeout(() =>{
      resolve('John wants to go home')
    }, 1000);
  });
}

jaehoon()
.then(data => {
  console.log(data)

  potter()
  .then(data => {
    console.log(data)

    john()
    .then(data => {
      console.log(data)
    })
  
  })

})

코드가 길어져 다시 가독성이 떨어지게 되는 대잔치가 벌어진다.
그렇다
Promise에게도 Promise 지옥이 존재했던 것이다.

그래서 최신 문법 ES7부터는 비동기 코드를 더 나은 방법으로 표현 할 수 있는 Async-Await가 등장했다.
아래에서 살펴보자.

Async - Await

ES7부터 적용이 된 비동기 처리 Async - Await는 Promise형식을 유지하고 있다.
하지만 이 함수는 "지옥"이라는 구렁텅이에서 빠져나오기 위해 가독성을 높이기 위해 보여지는 코드 모양을 변형했다.
바로 표면적으로 동기적 작용하는 것처럼 보이게 하고,
실제 실행하는건 promise와 동일하게 비동기적 작용을 하게 한다.
그래서 그런건지 잘은 모르겠지만, function 앞에 async라는 단어를 붙여주는 것도 눈여겨 보도록 하자.

또한 주의할 점은 Promise를 유지하고 있지만,
.then & .catch를 사용하여 컨트롤하는 것이 아니라 동기적 코드처럼 반환 값을 변수에 할당하여 작성할 수 있게끔 도와주는 문법이다.

그럼 코드를 살펴보도록 하자.


function jaehoon() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Jaehoon is studying JS");  
    }, 1000);
  });
};

function potter() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Potter wants to go home")
    }, 1000);
  });
};

function john() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("John wants to eat something");
    }, 1000);
  });
};  //-------여기까지는 promise와 동일하다. 이제부터 아래에 집중하기!

const result = async () => {   // 일단 비동기처리할 것(async)들을 result함수 안에 담는다.

  const a = await jaehoon(); // 위의 함수들을 async result함수 안에서 await로 실행
  console.log(a);
  
  const b = await potter();
  console.log(b);
  
  const c = await john();
  console.log(c);
};			// 표면적으로 동기적인 코드로 작성을 하였지만, 실제 기능은 비동기
			// 훨씬  보기 편해진 것을 체감할 수가 있음.
result();

/*
a부터 c까지 1초씩 지나면서 밑의 내용이 찍힌다.

 "Jaehoon is studying JS"
 "Potter wants to go home"
 "John wants to eat something"
*/

😎 Potter's thought

뭔가 처음부터 매우 헤맸던 문법이였다.
그래서 블로그를 정리하기가 매우 조심스러웠고, 사실 아직도 적응이 안되긴 한다.

그래도 블로그를 정리하면서 다시 손코딩을 해보니 어느정도는 감이 오는 듯 하다.

이렇게 정리를 하다보니
마냥 최신 문법이라고 async 를 무조건 사용할 필요가 없을 것 같다.
콜백은 프로미스나 async 처럼 별다른 키워드? 가 없이도 구현이 가능하기에
콜백 지옥이 아닌 간단한 비동기 처리를 할 때는 콜백을 사용하는게 더 편리하지 않을까 생각이 든다.

물론.. 막상 실무에서는 어떻게 사용이 될 지는 아직 감은 안오지만...

하 점점 어려워진다.....

참고: https://medium.com/@yoohl/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8F%99%EA%B8%B0-ac9495e42d0

profile
코딩에서 인생을 배우다.

0개의 댓글