자바스크립트의 비동기 처리

김수정·2019년 12월 6일
0

비동기처리란?

동기와 비동기는 서로 상반되는 말인데요. 우선, 동기는 A의 일이 끝나야만 B의 일이 끝나고 C의 일을 시작하는... 의 식으로 앞의 일이 끝나야만 뒤의 일을 진행할 수 있는 처리 방식을 말합니다. 비동기는 반대로 동시의 여러 함수를 호출하거나 하나의 일이 끝나기 전에 다른 함수를 호출할 수도 있습니다.

비동기로 처리를 하게 되면 동시에 여러 일을 할 수 있으므로 요즘에 많이 사용되는 기법입니다. 특히 자바스크립트는 단일 쓰레드 방식이라서 원래 동시에 여러 일을 할 수 없기 때문에 비동기 방식을 많이 사용합니다.

자바스크립트에서 사용하는 비동기 처리

  • Ajax Web API 요청: 만약 서버쪽에서 데이터를 받와아야 할 때는, 요청을 하고 서버에서 응답을 할 때 까지 대기를 해야 되기 때문에 작업을 비동기적으로 처리합니다.
  • 파일 읽기: 주로 서버 쪽에서 파일을 읽어야 하는 상황에는 비동기적으로 처리합니다.
  • 암호화/복호화: 암호화/복호화를 할 때에도 바로 처리가 되지 않고, 시간이 어느정도 걸리는 경우가 있기 때문에 비동기적으로 처리합니다.
  • 작업 예약: 단순히 어떤 작업을 몇초 후에 스케쥴링 해야 하는 상황에는, setTimeout 을 사용하여 비동기적으로 처리합니다.

비동기 처리 방법들

동시에 일을 처리한다는 것은 좋은데, 비동기 속에서도 동기적으로 처리되어야 하는 일들은 어떻게 해야할까요? 예를 들면 A에서 연산한 결과값을 가지고 B에서 사용해야 한다면요?

콜백함수

아주 전통적인 방법으로 콜백함수가 있습니다. 쉬운 방법이지만 비동기 코드가 조금만 많아지면 코드 가독성이 많이 떨어집니다. 아래는 숫자 n을 파라미터로 받아와서 1초마다 1씩 더해서 출력해야 하는 예제입니다.

function increaseAndPrint(n, callback) {
  setTimeout(() => {
    const increased = n + 1;
    console.log(increased);
    if (callback) {
      callback(increased);
    }
  }, 1000);
}

increaseAndPrint(0, n => {
  increaseAndPrint(n, n => {
    increaseAndPrint(n, n => {
      increaseAndPrint(n, n => {
        increaseAndPrint(n, n => {
          console.log('끝!');
        });
      });
    });
  });
});

어떤가요? 계속 옆으로 파고 들어가고 이해하기가 쉽지 않죠? 코드가 명료하게 보이지 않습니다.
또한 콜백처리는 try catch로 에러를 잡아낼 수도 없습니다.

Promise

promise는 체이닝 방식으로 콜백지옥을 해결해줍니다. promise를 리턴한다면 .then 메소드를 통해 이어서 작업할 수 있습니다. 또한 성공, 에러 처리가 가능합니다.
먼저 생성자 함수를 통해 promise를 만들어 성공 시, 실패 시 처리를 설정합니다.

function increaseAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = n + 1;
      if (value === 5) {
        const error = new Error();
        error.name = 'ValueIsFiveError';
        reject(error);
        return;
      }
      console.log(value);
      resolve(value);
    }, 1000);
  });
}

increaseAndPrint(0)
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .catch(e => {
    console.error(e);
  });

이 외에도 promise.all, promise.race 등이 있습니다.

  • promise.all: 동시에 작업을 하고 싶을 때 사용. 연결된 작업 중 하나라도 실패하면 모두 실패한 결과를 가져옴.
const results = Promise.all([getDog(), getRabbit(), getTurtle()]);
  • promise.race: 여러개의 프로미스를 등록해서 실행했을 때 가장 빨리 끝난것 하나만의 결과값을 가져옴.
const first = Promise.race([getDog(), getRabbit(), getTurtle()]);

async-await

async-await는 promise를 좀 더 쉽게 사용하고, 절차지향적 코드와 잘 맞습니다.
promise가 있는 함수에 async를 붙이고, 해당 promise함수에 await를 붙여 사용하면 동기로 사용이 됩니다. 또한 async를 사용한 함수는 자동으로 promise를 리턴하게 되어있어 체이닝 사용이 가능합니다.

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

const getDog = async () => {
  await sleep(1000);
  return '멍멍이';
};

const getRabbit = async () => {
  await sleep(500);
  return '토끼';
};
const getTurtle = async () => {
  await sleep(3000);
  return '거북이';
};

async function process() {
  const dog = await getDog();
  console.log(dog);
  const rabbit = await getRabbit();
  console.log(rabbit);
  const turtle = await getTurtle();
  console.log(turtle);
}

process();
profile
정리하는 개발자

0개의 댓글