비동기 프로그래밍(Asynchronous)에 대하여

개발자 베니·2021년 7월 19일
1
post-thumbnail

동기적인 것은 정직하다.

라멘집에서 일하는 직원이 한 번 되봅시다! 저는 라멘에 굉장히 진심인 사람이라서, 손님이 주문을 하시면 최고로 맛있는 라멘을 끓이기 위해서 요리하는 것에만 집중을 하는 사람입니다! 라멘은 보통 5분이면 나오게 되는데, 첫 번째 손님이 라멘을 주문하시면 저는 5분 동안 그 라멘을 최고로 맛있게 끓이기 위해서 이 라멘만을 보고 있습니다. 그렇게 집중하고 있는 사이에 두 번째 손님이 들어오시네요!

온전히 라멘에만 집중하고 있는 저는 주문 받을 여유조차 없습니다.

그저 이 라멘을 우선적으로 맛있게 끓이는 것에만 몰두하고 있어요. 두 번째 손님은 그렇게 라멘이 다 끓여지는 5분동안 가만히 기다리고 있습니다. 그리고 라멘이 나온 후 두 번째 손님은 라멘과 새우튀김을 주문하셨습니다!

새우튀김은 3분이 걸리고 라멘은 5분이 걸리는 데, 저는 우선 라멘을 끓인 후에 새우튀김을 만들겁니다. 그런데 이어서 세 번째, 네 번째 손님들 또한 들어오고 있습니다.

저는 요리에 집중을 하고 있기 때문에 이 후에 들어온 손님들 또한 그저 기다리고 있어야 합니다. 아마도 저는 오늘안에 모든 손님에게 라멘을 제공하기는 조금 어려워보입니다.

굉장히 정직한 저는 아마도 사장님께서 그만 나와도 될 것 같다고 말할 것 같네요..

자바스크립트는 싱글 스레드이다.

자바스크립트는 위의 직원처럼 한 번에 한 가지 일만 할 수가 있습니다. 동기적으로 처리한다고 볼 수 있죠. 굉장히 단순하게 일을 처리하는 자바스크립트를 어떻게 하면 효과적으로 움직일 수 있게끔 만들어 줄 수 있을까요?

그것이 바로 비동기적 프로그래밍입니다.

만약에 유튜브가 동기적으로 움직였다면 어떻게 됬을까요? 아마 불러오는 데이터를 하나하나 다 처리하고 움직이기 때문에, 저희는 데이터가 다 처리될 동안 가만히 있어야 겠죠.

기본적 예시.

console.log(“Before timeout”);
setTimeout(() => {
 console.log("Executed!");
}, 5000);
console.log("After timeout");

콘솔에 로그를 찍어낼 것인데, 가운데에 setTimeout이라는 함수를 사용해서 5000ms 즉 5초 후에 함수가 실행되도록 설정을 해놨습니다.

결과는 어떻게 되었을까요?

Before timeout
After timeout
Excuted!

자바스크립트는 모든 작업을 순서대로 콜스택에 집어넣고 실행합니다. 잘 실행하다가, setTimeout을 보고는 WEBAPI에 타임에 관련된 api에 넣어주고 5초뒤에 실행하게끔 한 후에 쌓여진 콜스택을 다시 처리해 나갈 것입니다.

이후에 콜백 큐에 들어와서 다시 한번 이벤트를 실행하게 되겠죠!

이렇게 setTimeout을 사용했을때 저희는 비동기적으로 일을 처리하게 되었습니다! 위의 라멘집 직원을 예시로 들자면 라멘을 만들기 위해 재료를 손질하다가 시간에 맞춰서 새우튀김을 기름기에 넣어줘서 튀기는 것과 비슷한 맥락이라고 볼 수 있을 것 같습니다. 이렇게 되면 라멘집의 일은 좀 더 매끄럽게 진행이 되겠죠!

이런 식으로 자바스크립트도 콜백을 이용해서 비동기 프로그래밍을 구현할 수 있게 되었습니다!

콜백지옥

모든지 원하는 대로 잘 풀리면 그것은 코딩이 아니겠죠. 위에 처럼 setTimeout 하나만 있는 경우라면 구별하기도 쉽고 별로 어렵지 않겠지만, 만약에 하나가 아니고 중첩이 되서 여러번 나타난다면 그리고 중간에 에러라도 난다면 얼마나 당황스러울까요?

딱 봐도 가독성이 굉장히 떨어지고 중간에 에러가 발생한다면 고치기도 힘들어 보입니다. 이러한 경우를 콜백지옥에 빠졌다고 합니다.

해답 Promise

이러한 경우를 해결하기 위한 방법으로 Promise가 나왔습니다.

let myFirstPromise = new Promise((resolve, reject) => {
  setTimeout(function(){
    resolve("Success!"); 
  }, 250);
});

myFirstPromise.then((successMessage) => {
  console.log("Yay! " + successMessage);
});

비동기적 작업을 하는 함수의 리턴타입으로 쓰이는 것이 Promise입니다. Promise는 동작이 정상적으로 작동했을 때 사용하는 콜백함수 resolve와 동작이 정상적이지 않을 때 사용하는 reject를 인자로 받는 함수입니다. 프로미스는 콜백으로 비동기적 작업을 할 때의 단점을 보완하기 위해서 나왔습니다.

콜백만 사용했을 때 나타나는 현상들. 예를 들어 형태가 말도안되게 길어진다던가, 찾기 힘든 버그들을 더 쉽게 해결할 수 있게끔 해줄 수 있게 됬습니다.

Promise는 3가지 상태를 가집니다.

  1. Pending(미결) : 동작이 정상 작동하는 지, 아닌 지 모르는 상태.
  2. Fulfilled(이행) : 동작이 정상 작동되는 상태.
  3. Rejected(거절) : 동작이 정상 작동이 되지 않아 거절된 상태.

코드로 예시를 보기 위해서 제이크님의 블로그에 있는 예시를 보겠습니다.

// ES 5 //
var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
  function (resolve, reject) {
    if(isMomHappy) {
      var phone = {
        brand: 'Samsung',
        color: 'black'
      };
      resolve(phone); //fulfilled
    }
    else {
      var reason = new Error('mom is not happy');
      reject(reason); //reject
    }
  }
);

만약 엄마의 기분이 좋다면 검정색 삼성 핸드폰을 가지게 될 것이고, 만약 엄마의 기분이 좋지 않다면 아무것도 가지지 못하게 될 것입니다! 프로미스를 만들었으니 한번 실제로 사용을 해보겠습니다.

// ES 5 //
...

// Promise 호출
var askMom = function () {
  willGetNewPhone
    .then(function (fulfilled) {
      // 와! 새 폰을 얻었다!
      console.log(fulfilled);
    // output: { brand: 'Samsung', color: 'black' }
    })
    .catch(function (error) {
      // 이런. 엄마가 폰을 안사주네..
      console.log(error.message);
    // output: 'mom is not happy'
    });
}

위의 예시는 직접 한번 변수 isMomHappy를 수정해보시면 결과가 달라지는 것을 확인하실 수 있습니다.

Promise chaining(연계)

프로미스는 연계가 가능합니다.

위의 예시에서 아이가 핸드폰을 얻게 된다면 친구에게 자랑하기로 Promise(약속)했다고 생각해봅시다.

새로운 프로미스가 등장합니다.

var showOff = function (phone) {
  return new Promsie(
    function (resolve, reject) {
      var message = 'Hey friend, I have a new ' +
          phone.color + ' ' + phone.brand + ' phone';
      
      resolve(message);
    };
  );
};

showOff 프로미스의 연계는 willGetNewPhone Promise를 수행한 이후에만 가능합니다.

var askMom = function () {
  willGetNewPhone
  .then(showOff) // 여기서 연계합니다.
  .then(function (fulfilled) {
    console.log(fulfilleded);
    // output: 'Hey friend, I have a new black Samsung phone.'
  })
  .catch(function (error) {
    // oops, mom don't buy it
    console.log(error.message);
  // output: 'mom is not happy'
  });
};

위의 상황에서 willGetNewPhone Promise에서 에러없이 작동을 했다면 분명히 검정색 삼성 핸드폰을 리턴할 것입니다.

그 정보는 then을 이용해서 또 다른 Promise인 showOff로 들어가서 핸드폰의 색깔과 정보를 받을 수 있게끔 했습니다. 콜백을 리턴해서 받은 곳도 콜백이 되는 것입니다. 위에서 콜백 지옥의 예시처럼 지저분하지 않고 깔끔하게 잘 정리가 되어있는 모습이죠.

이 후에 친구에게 자랑을 하는 콘솔 로그가 나타나는 것을 확인할 수 있겠네요.

Promise는 비동기적이다.

이미 우리는 프로미스가 비동기적이라는 것을 알고 있지만, 좀 더 확실하게 예시를 보면서 이해하는게 좋을 것 같아서 제이크님의 예시를 보면서 공부를 해보겠습니다.

var askMom = function () {
  console.log('before asking Mom'); // log before
  willGetNewPhone
    .then(showOff)
    .then(function (fulfilled) {
      console.log(fulfilled);
    })
    .catch(function (error) {
      console.log(error.message);
    });
  console.log('after asking Mom');
}

논리적으로 우리의 코드는 위에서 아래로 실행이 되어서 결과가 이런 식으로 나올 것이다 라는 생각을 하게됩니다.

  1. before asking Mom
  2. Hey friend, I have a new black Samsung phone.
  3. after asking Mom

그렇지만 결과는 다릅니다.

  1. before asking mom
  2. after asking mom
  3. Hey friend, I have a new black Samsung phone.

실제로는 자바스크립트가 Promise를 발견한 후 바로 WEB APIs로 넘겨진 후에 다음 콜 스택을 실행하게 됩니다.

Async/ Await

ES7에 등장한 Async/ Await은 저희가 작성한 코드를 좀 더 예쁘게 그리고 깔끔하게 작성할 수 있도록 도와줍니다! 심지어 여기서는 then과 catch를 작성하지 않고도 표현이 가능합니다.

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise((resolve, reject) => {
  if (isMomHappy) {
    const phone = {
      brand: "Samsung",
      color: "black",
    };
    resolve(phone);
  } else {
    const reason = new Error("mom is not happy");
    reject(reason);
  }
});

// 2nd promise
async function showOff(phone) {
  return new Promise((resolve, reject) => {
    var message =
      "Hey friend, I have a new " + phone.color + " " + phone.brand + " phone";

    resolve(message);
  });
}

// call our promise
async function askMom() {
  try {
    console.log("before asking Mom");

    let phone = await willIGetNewPhone;
    let message = await showOff(phone);

    console.log(message);
    console.log("after asking mom");
  } catch (error) {
    console.log(error.message);
  }
}

(async () => {
  await askMom();
})();

결론

처음에 비동기에 대해서 글을 작성하려고 했는데, 작성하다 보니까 이것도 작성하면 좋겠다. 저것도 써야지! 라는 생각이 많아져서 글이 너무 길어졌습니다.

사실 말하고 싶었던 것은 비동기라는 것은 컴퓨터가 많은 일을 마치 사람이 처리하듯이 할 수 있게끔 해주는 것이라는 겁니다.

저희 사람은 살다보면 주어진 상황에 맞게 유동적으로 일을 진행합니다. 라멘을 끓이는 직원이 하나의 라멘을 우선적으로 집중해서 끓이고 서빙하고 주문을 받고 하는 형식이 아니고, 라멘을 끓이던 도중에도 주문이 들어오면 물 올려놓고 주문 받고 상황에 맞게 행동을 하죠.

비동기라는 것이 그런겁니다.

위의 어린 아이가 핸드폰을 부모님께 사달라고 했을 때도, 아이는 부모의 결정이 내려질 때까지 가만히 있는게 아니라, 놀이터에 가서 친구와 놀거나 잠도 자고 공부도 하고 하겠죠. 이러한 과정처럼 컴퓨터를 다루는 것이 비동기 프로그래밍이라고 생각합니다.

원래는 AJAX에 대한 내용도 작성을 하려했는데 다음 포스팅으로 미루겠습니다!

도움 받은 블로그

--Two_jay님 블로그--

https://velog.io/@two_jay/callback-%EA%B3%BC-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

--jakeseo님 블로그--

https://velog.io/@jakeseo_me/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-33%EA%B0%80%EC%A7%80-%EA%B0%9C%EB%85%90-25-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%94%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-Promise

--Kwoncheol Shin님 글 --

https://medium.com/@kwoncharles/js-%EB%B9%84%EB%8F%99%EA%B8%B0-async-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1-7dc99ecf4ca6

--MDN Promise--

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

0개의 댓글