본문 바로가기
Front-End/JavaScript

자바스크립트 비동기 처리 방식

by 연제원 2021. 2. 2.

✅ 비동기(Asynchronous) 란?


비동기는 하나의 요청 처리가 완료되기 전에 다음 요청을 처리하는 방식이다. 즉 순서에 상관없이 동시에 여러개의 작업을 할 수 있기 때문에 효율적이다.

 

 

왜 처리를 해야하는가? 

우선 자바스크립트는 싱글스레드 방식이라 동기적으로밖에 진행할 수 없는데, 기본적으로 자바스크립트는 웹브라우저나 node.js 엔진에서 실행이 된다. 이때 엔진에는 자바스크립트를 돌리는 하나의 스레드가 존재하는데, 엔진 뿐 아니라 비동기 처리모델인 web api라는 것이 함께 동작해서 setTimeout 같은 시간이 소요되는 일을 처리한다. web api들이 자바스크립트 엔진 스레드와는 따로 비동기 처리를 해주면서 콜백함수를 가지고 이벤트 루프에 들어가 처리되는 대로 콜백함수를 자바스크립트 엔진으로 보내준다. 이때 setTimeout함수 같은 경우 비동기적으로 진행되기 때문에 콜백함수 과정이 끝나기도 전에 다음 프로세스를 진행하게 되는 경우가 생긴다. (예를 들면, 값을 받아서 그 값을 사용해야하는데 값을 받지못하고 진행이 되어 undefined값을 받게 됨) 이러한 문제점들을 해결하기 위해 여러 해결 방법들이 존재한다.


1. Callback 함수


callback함수는 다른 함수의 인자로 사용되거나 이벤트에 의해 호출되어지는 함수를 뜻한다. 즉, 어떤 함수의 요청이 처리되어 나온 그 응답(값)을 callback하여 다음 함수에서 사용할 수 있는 것을 callback이라 부른다. callback함수는 비동기적으로 작성된 함수들을 사용자의 편의에 따른 순서로 동기처리가 가능하게 한다.

 

우선 callback함수를 쓰기 전 비동기 방식의 문제점을 보자.

function getData() {
  let data;

  setTimeout(function () {
    data = 'result';
  }, 1000);

  return data;
}

console.log(getData()); // undefined

예상대로라면 1초뒤에 'result'가 출력되어야 하는데 다른 결과 값이 나왔다. 그 이유는 setTimeout()이 비동기적으로 진행되기 때문이다.

우리가 원하는대로 바꾸기 위해 callback 함수를 사용해보자

function getData(callback) {
  let data;

  setTimeout(function () {
    data = 'result';
    callback(data);
  }, 1000);
}

getData(function (data) {
  console.log(data); // result
});

 

함수를 실행할 때 인자 값으로 콜백 함수를 넣어주면 해결이 가능하다. getData()의 인자값으로 콜백 함수가 들어갔고, setTimeout()이 1초 뒤 실행되고 결과값을 콜백 함수의 인자값으로 넣어 호출해준 것이다. 이렇게 콜백 패턴을 사용하면 특정 로직이 끝났을 때 원하는 동작을 실행 시킬 수 있다.

 

하지만 이러한 방식에도 문제점이 있다. 바로 콜백 헬...

 

callback hell

이름부터 보기 안좋은 콜백 지옥

비동기 처리를 위해 콜백 패턴을 사용하면 다음과 같이 여러 개의 콜백 함수가 중첩이 되어 가독성이 매우 떨어진다.

step1(function (value1) {
  step2(function (value2) {
    step3(function (value3) {
      step4(function (value4) {
        step5(function (value5) {
          // 탈출
        });
      });
    });
  });
});

 


✅ 2. Promise


위에서 나온 콜백 함수의 문제점을 해결하기 위해 나온 것이 Promise이다.

이것은 동기적으로 보이는, 비동기 처리 방식이다.

 

프로미스는 Promise 생성자 함수를 통해 인스턴스화한다. Promise 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인자로 전달받는다.

 

프로미스의 생성

// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
  // 비동기 작업을 수행한다.

  if (/* 비동기 작업 수행 성공 */) {
    resolve('result');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure reason');
  }
});

// 헷갈리지 말것
// new Promise(() => {}) Promise 생성자 함수가 비동기 작업을 수행할 콜백 함수를 인자로 전달받음
// ((resolve, reject) => {}) 콜백 함수의 인자가 resolve, reject 함수

 

프로미스의 3가지 상태

프로미스를 사용할 때 가장 중요한 개념이 프로미스의 상태다. 여기서 상태란 프로미스의 처리 과정을 뜻한다.

new Promise() 로 프로미스를 생성하고 종료할 때 까지 3가지 상태를 갖는다.

  • Pending(대기) : 비동기 처리가 아직 수행되지 않은 상태
  • Fulfilled(이행) : 비동기 처리가 수행된 상태 (성공) = resolve 함수가 호출된 상태
  • Rejected(실패) : 비동기 처리가 수행된 상태 (실패) = reject 함수가 호출된 상태

1. Pending(대기)

new Promise() 메서드를 호출하면 대기 상태가 된다.

new Promise();

new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject 이다.

new Promise(function(resolve, reject) {
  // ...
});

 

2. Fulfilled(이행)

콜백 함수의 인자 resolve를 실행하면 이행상태가 된다.

new Promise(function(resolve, reject) {
  resolve();
});

그리고 이행 상태가 되면 then()을 이용하여 처리 결과 값을 받을 수 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    var data = 100;
    resolve(data);
  });
}

// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
  console.log(resolvedData); // 100
});

 

3. Rejected(실패)

콜백 함수의 인자 reject를 실행하면 실패상태가 된다.

new Promise(function(resolve, reject) {
  reject();
});

그리고 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()을 이용하여 처리 결과 값을 받을 수 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

 

프로미스의 후속 처리 메서드

Promise로 구현된 비동기 함수는 Promise 객체를 반환하여야 한다. Promise로 구현된 비동기 함수를 호출하는 측에서는 Promise 객체의 후속 처리 메소드를 통해 비동기 처리 결과 또는 에러 메시지를 전달받아 처리한다. 

위에서 프로미스 상태에 따라 then(), catch() 를 통해 각각 이행, 실패 상태에 대한 후속처리를 하는 것을 알 수 있었다. 여기에 추가로 finally() 도 존재한다.

 

1. then

then 메소드는 두 개의 콜백 함수를 인자로 전달 받는다. 첫 번째 콜백 함수는 성공(fulfilled, resolve함수가 호출된 상태) 시 호출되고, 두 번째 함수는 실패(rejected, reject 함수가 호출된 상태) 시 호출된다.

then 메소드는 Promise를 반환한다.

 

2. catch

예외(비동기 처리에서 발생한 에러와 then 메소드에서 발생한 에러)가 발생하면 호출된다.

catch 메소드는 Promise를 반환한다.

추가로, 에러를 처리하는 방법은 then의 두번째 인자로 처리하는 것과 catch를 이용하는 것 두 가지가 존재하는데, catch를 사용하는 것을 권장한다.

 

3. finally

성공, 실패 상관없이 무조건 지정된 콜백 함수가 실행된다.

finally 메소드는 Promise를 반환한다.

 

⭐ 프로미스 Chaining (체이닝)

위에서 본 후속처리 메서드들은 전부 프로미스를 반환한다. 그렇기 때문에 연속적으로 호출이 가능한데, 이를 프로미스 체이닝이라고 한다.

 

✅ async / await

sync와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.

async function 함수명() {
  await 비동기 처리 메서드(); 
}

1. async : 함수명 앞에 사용, 주의할 점은 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야 한다.

2. await : 비동기 처리 메서드 앞에 사용

 

예제

function fetchItems() {
  return new Promise(function(resolve, reject) {
    var items = [1,2,3];
    resolve(items)
  });
}

async function logItems() {
  var resultItems = await fetchItems();
  console.log(resultItems); // [1,2,3]
}

fetchItems() 함수는 프로미스 객체를 반환하는 함수(= 비동기 처리 메서드)다.

logItems() 함수 앞에 async를 사용해주고 처리할 비동기 처리 메서드앞에 await를 사용해준다.

댓글