동기, 비동기처리

oceanzoo·2021년 7월 10일
post-thumbnail

동기 (Synchronous)와 비동기(Asynchronous)

동기처리 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다. 직렬적으로 작업을 수행하는 방식이다. 서버에 데이터를 요청하고 데이터가 응답될 때까지 이후 태스크들은 블로킹(blocking, 작업 중단)된다.

비동기처리 방식은 요청을 보냈을 때 상태와 상관없이 다음 동작을 수행할 수 있다. 병렬적으로 작업을 수행한다. 결과값이 나오는대로 작업이 출력된다. 서버에 데이터를 요청한 이후 서버로부터 데이터가 응답될 때까지 대기하지 않고(Non-Blocking) 즉시 다음 태스크를 수행한다. 자바스크립트의 대부분의 DOM 이벤트와 Timer 함수(setTimeout, setInterval), Ajax 요청은 비동기식 처리 모델로 동작한다.

비동기적 코드의 실행 결과는 동기적 코드가 전부 실행 되고나서 값을 반환한다.

console.log('1');

setTimeout(()=> {
 console.log('2');
},3000);

console.log('3');

javascript에서는 싱글스레드 기반 언어라 비동기 처리를 하기 때문에 1,2,3 순차대로 출력이 되지않고, 1이 출력되고 setTimeout으로 3초간 기다리는 것을 뒤로하고 3이 먼저 출력되고 2가 출력된다.

장단점

동기 :
장점 ) 설계가 매우 간단하고 직관적이다.
단점 ) 결과가 주어질 때까지 아무것도 못하고 대기해야한다.

비동기 :
장점) 결과가 주어지는데 시간이 걸리더라도 그 시간동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다.
단점) 설계가 동기식보다 복잡하다.


비동기 처리는 결과를 예측할 수 없기 때문에 `동기식의 처리`가 필요하다. 대표적으로 `promise, callback`이 있고 `async&await`는 기존 동기식 처리의 단점을 보완하고 가독성을 높혀주는 코드를 작성할 수 있다.

promise

자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 object이다.
정해진 장시간의 기능을 수행하고나서 정상적으로 기능이 수행되어졌다면 성공의 메세지와 함께 결과값을 전달해주고, 기능수행하다가 예상치 못한 문제가 발생하면 에러를 전달한다.

promise의 상태는 promise가 만들어져서 우리가 지정한 operation이 진행중일 때는 pending, operation을 성공적으로 다 끝내고나면 fulfilled상태가 된다. 파일을 찾을 수가 없거나 네트워크에 문제가 있다면 rejected상태가 된다.

우리가 원하는 기능을 수행해서 해당되는 데이터를 만들어내는 producer가 있고, 우리가 원하는 데이터를 소비하는 consumer로 나누어진다.

promise 만들기

1. Producer

const promise = new Promise((resolve, reject) => {
  console.log('doing something');
  setTimeout(() => {
    resolve('ellie');
  } , 2000 );
}) 

promise는 클래스이기 때문에 new 라는 키워드를 통해서 object를 생성할 수 있다.
promise의 생성자는 executor라는 콜백함수를 전달해줘야하는데 이 콜백함수는 또다른 두가지의 콜백함수를 받는다.
기능을 정상적으로 수행해서 마지막에 최종 데이터를 전달하는 resolve 콜백함수와 기능을 수행하다가 중간에 문제가 생기면 호출하게되는 reject가 있다.

네트워크 통신이나 파일을 읽어오는 것과 같이 시간이 오래 걸리는 일들을 동기적으로 처리하게되면 다음 라인의 코드가 실행되지 않기 때문에 비동기적으로 처리해주는 것이 좋다.

새로운 Promise가 만들어질 때는 excutor라는 함수가 자동적으로 바로 실행된다.

Promise 사용하기

2. Consumers

promise.then(value => {
  console.log(value); // ellie
});

consumer는 then, catch, finally를 통해서 값을 받아올 수 있다.
then은 promise가 정상적으로 잘 작동되어서 최종적으로 resolve라는 콜백함수를 통해서
전달한 값이 (여기서는 ellie) value 파라미터로 전달되어서 들어온다.

그렇다면 네트워크를 하다가 무언가 실패해서 reject 호출한다면?

const promise = new Promise((resolve, reject) => {
  console.log('doing something');
  setTimeout(() => {
    reject(new error('no network'))
  } , 2000 );
}) 

error라는 object를 통해서 값을 전달한다. 자바스크립트에서 제공하는 object 중 하나이다.
error object에는 왜 실패했는지 이유를 적어주면 된다.

promise
  .then(value => {
  console.log(value); 
  })
  .catch(error  => {
  	console.log(error);
   });
  .finally(() => {
    console.log('finally');
  });

then을 통해서 성공적인 케이스를 다뤘다면, catch함수를 통해서 error가 발생했을 때 어떻게 처리할것인지 콜백함수를 등록해주면 된다. finally는 성공,실패 여부와 상관없이 마지막에 호출된다.

async & await

깔끔하게 promise를 사용할 수 있는 방법

1. async

async function fetchUser() {
  //do network request in 10 secs...
  return 'ellie';
}

const user = fetchUser();
user.then(console.log);
console.log(user);

2. await

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

async function getApple(){
  await delay(3000);
  return '🍎';
}

async function getBanana(){
  await delay(3000);
  return '🍌';
}

아래처럼 chaining을 하는 것보다 위와 같이 동기적 코드를 만드는 것처럼 보이게 하면 더 쉽게 이해가 가능하다.

function getBanana(){
  return delay(3000)
  .then(()=>'🍌');
}

하이라이트
사과와 바나나 둘 다를 따오는 함수

async function pickFruits(){
  const apple = await getApple();
  const banana = await getBanana();
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log); // 6초 후에 🍎 +🍌

순차적으로 처리를 할 경우에는 비효율적이다.
바나나를 받아오는 것과 사과를 받아오는 것은 서로 연관이 되어있지 않기 때문에 서로 기다릴 필요가 없다.
이를 개선하기 위해서는

await 병렬 처리

async function pickFruits(){
  const applePromise = getApple();
  const bananaPromise = getBanana();
  const apple = await applePromise;
  const banana = await bananaPromise;
  return `${apple} + ${banana}`;
}

pickFruits().then(console.log);

3초 후에 🍎 +🍌 실행된다

이 코드보다 더 간결하게 사용할 수 있는 방법이 있는데
Promise에 있는 all이라는 API를 사용하면 된다.

function pickAllFruits(){
  return Promise.all([getApple(), getBanana()]).then(fruits => 
    fruits.join('+')
  );
}

pickAllFruits().then(console.log);

만약 하나의 과일만 받아오는 함수를 만들고 싶다면 이때는 race라는 API를 이용하면 되는데,
race는 배열에 전달된 Promise 중에서 가장 먼저 값을 리턴하는 것만 전달이 되어진다.

async function getApple(){
  await delay(2000);
  return '🍎';
}

async function getBanana(){
  await delay(1000);
  return '🍌';
}

function pickOnlyOne(){
  return Promise.race([getApple(), getBanana()]);
}

pickOnlyOne().then(console.log) //🍌

출처: https://webclub.tistory.com/605 [Web Club] / 유튜브 '드림코딩 by 엘리'

profile
준비된 개발자를 위한 날갯짓 🦋

0개의 댓글