Javascript

[javascript] 자바스크립트 동기 / 비동기에 대한 이해

배고픈개발자 2023. 3. 19. 11:55

자바스크립트는 동기식으로 작동합니다.

동기란 코드가 순서대로 작동하는 것입니다.

 

식당을 예로 들어 설명해 보겠습니다. 

만약 동기적인 방식으로 운영되는 식당이 있다면 주문을 받은 순서대로 음식을 만들고 제공할 것입니다.

바베큐 - 라면 순으로 주문을 받았다면 바베큐가 완료될 때까지 라면을 요리하지 않습니다.

 

이렇게 된다면 라면 음식을 시킨 고객은 불편함을 겪을 수 없습니다.

이럴 때 필요한 것이 비동기적 방식의 운영입니다.

바베큐가 완료될 때까지 기다리지 않고 바베큐를 불에 올려 놓은 채 라면을 주문한 고객에게 라면을 제공합니다. 

 

코드를 통해 위의 식당 예시를 살펴보겠습니다.

 

console.log('바베큐');
console.log('라면');

 

위 코드의 결과는 바베큐, 라면이 순서대로 출력됩니다.

자바스크립트의 원래 동작 방식인 동기적 방식입니다.

 

 

setTimeout(function() {
    console.log('바베큐');
}, 10000);


console.log('라면);


// 라면
// 바베큐

 

하지만 다음과 같이 setTimeout을 사용한다면 비동기적 작업이 가능합니다.

바베큐 주문을 먼저 받았지만 라면이 먼저 제공되고 바베큐는 10초 뒤에 제공됩니다.

 

 

그럼 비동기는 언제 사용되나요?

 

서버로부터 데이터를 불러올 때 사용할 수 있습니다.

만약 해당 요청의 시간이 오래 걸린다면 비동기식으로 데이터를 요청하고, 데이터가 도착하기 전에 다른 작업을 수행할 수 있습니다.

 

비동기 사용법

1. Promise 

 

콜백지옥을 해결할 수 있는 기법 중 하나입니다.

Promise는 resolve와 reject라는 두 개의 파라미터를 받습니다.

resolve는 성공했을 때, reject는 에러가 발생했을 때 사용합니다. 

파라미터의 이름은 꼭 resolve와 reject로 하실 필요없이 res, rej 등 원하시는 대로 쓰셔도 됩니다.

 

const getData = () => {
  return new Promise((res, rej) => {
    const Success = true; 
    // 특정 작업을 수행
    // 예를 들면 데이터 요청
    
    if (Success) {
      res({ data: "성공입니다." });
    } else {
      rej({ data: "실패입니다." });
    }
  });
};

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

// {data: '성공입니다.'}

 

다음과 같이 작업이 성공했을 때 res 함수를 통해 fullfilled이라는 promise 값을 반환합니다.

그리고 getData 호출을 통해 fullfilled 값을 반환 받았을 때 then을 통해 성공했을 때 작업을 수행할 수 있습니다.

 

 

const getData = () => {
  return new Promise((res, rej) => {
    const Success = false;
    if (Success) {
      res({ data: "성공입니다." });
    } else {
      rej({ data: "실패입니다." });
    }
  });
};

getData()
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });
  
  
  // {data: '실패입니다.'}

 

작업이 실패했을 경우에는 catch 구절을 통해 에러 핸들링을 할 수 있습니다. 

 

Promise는 fullfilled, pending, rejected 세 가지 상태 값 중 하나를 반환합니다.

fullfilled을 받았을 때는 비동기 작업이 완료 됐다는 의미이고, rejected는 비동기 작업이 실패했다는 의미입니다.

위의 예제에서는 res() 함수가 호출됐을 때 fullfiled 값을 반환하고, rej() 함수가 호출됐을 때 rejected 값을 반환합니다.

마치 약속을 한 것처럼 성공했을 때 실행할 함수, 실패했을 때 실행할 함수를 정해놓은 것과 같습니다.

 

1. async await

 

const getData = async () => {
  return 1;
};

console.log(getData());

//Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 1, 
Symbol(async_id_symbol): 5, Symbol(trigger_async_id_symbol): 1, Symbol(destroyed): {…}}

 

async는 function 앞에 선언합니다.

function을 async로 감싸면 항상 promise 값을 반환하게 됩니다.

 

만약 function이 promise가 아닌 값을 반환하여도 promise로 만들어서 반환하여 줍니다.

Promise 상태는 fulfilled, 1이라는 값이 반환된 것을 볼수가 있습니다.

 

const getData = async () => {
  return Promise.resolve(1);
};

console.log(getData());

 

또는 다음과 같이 명시적으로 Promise 값을 만들어서 반환할수도 있습니다.

 

 

await 사용법

const barbecue = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("바베큐");
  }, 10000);
});

async function order() {
  const food = await barbecue;
  console.log(food);
  console.log("라면");
}

order();

// 바베큐
// 라면

 

await은 async 안에서만 사용이 가능합니다.

async 안에서는 await 만나게 되면 해당 작업이 완료될 때까지 다른 작업을 수행하지 않고 기다리게 됩니다.

 

Promise에서 특정 작업을 수행하고 then을 실행하여 다음 작업을 실행한 것과 같습니다. 

이 코드만 보면 '바베큐와 라면이 차례대로 나왔는데 왜 async await이 비동기지?' 라고 생각하실수도 있습니다.

 

const barbecue = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("바베큐");
  }, 10000);
});

async function order() {
  console.log("바베큐 불에 올려 놓는다!");
  const food = await barbecue;
  console.log(food);
}

order();
console.log("라면");


// 바베큐 불에 올려 놓는다!
// 라면
// 바베큐

 

order 함수 안이 아니라 밖에서 보면 await async는 완벽하게 비동기적으로 작동하고 있습니다.

order가 먼저 호출돼서 바베큐에 불을 올려놨는데도 불구하고 10초가 걸리는 작업을 만나니

시간이 적게 걸리는 라면을 먼저 제공하고 바베큐가 완료됐을 때 바베큐를 제공하게 됩니다.