웹 비동기처리 콜백 to async

Kyung yup Lee·2021년 1월 12일
1

자바스크립트

목록 보기
2/12

비동기

비동기 처리라고 많이 얘기를 하는데, 나는 비동기 대기 처리라는 표현이 더 적절하다고 생각한다. 비동기처리 라는 단어는 마치 코드를 비동기로 만들겠다는 어감이 강한 것 같다. 하지만 실제론 비동기적으로 작동하는 코드를 어떻게 동기적으로 작동시켜 데이터를 받아올 때 까지 대기시킬 수 있는지를 말한다. 왜냐하면 결과값이 필요한 코드를 비동기적으로 데이터를 받아오기 전에 실행시켜버리면 에러가 발생할 수 밖에 없기 때문이다.

비동기의 의미는 asynchronous, 싱크가 안맞는다는 뜻이다.

흔히들 자막과 소리가 안 맞을 때 싱크가 안 맞네라고 말한다.

즉, 자막과 소리가 동시에 발생해야지 synchronous 하다고 한다.

이것을 프로그래밍 개념에 접목시켜서 이해하려고 하면 쓰레드의 관점에서 생각해야 한다.

완벽하게 쓰레드에 빙의해 보면, 난 메인쓰레드다.

사용자는 나에게 api 요청 혹은 네트워크 요청(데이터를 받아오는 데 시간이 걸리는) 을 지시했다.

동기 방식으로 처리하면 난 api 요청을 보내고 아무 transaction을 처리하지 않고 대기한다. 대기하는 동안 쓰레드가 죽어있다고 생각하고 대기가 끝나고 쓰레드가 다시 살아난다면, api 요청과 동시에 결과를 받아오는 느낌일 것이다.

반면 비동기 방식으로 처리하면 난 api요청을 보내고 다른 transaction 들을 계속 처리한다(예를 들어 ui 렌더링 작업)

그러면 요청과 결과는 동시에 이루어지지 않는다. 쓰레드는 계속 살아있었으니까.

위 예가 정확한 예시는 아니지만 이해를 돕기 위해 들어봤다.

비동기 처리 방식

비동기로 코드를 실행하면 먼저 실행되어야 하는 코드가 끝나기도 전에 다른 코드가 실행되는 경우가 발생할 수 있기 때문에, 반드시 선행 데이터를 받아오거나 하는 경우 이 비동기를 처리해주는 방식이 필요하다.

콜백함수

웹에서 데이터를 받아오는 동안 다른 화면으로 전환을 가능하게 하거나 로딩화면을 띄워주는 비동기적인 처리는 es6 이전 문법에서는 콜백함수를 통해 구현했다.

콜백함수는 함수(1)에 함수(2)를 인자로 받아 함수(1) 의 처리가 끝나고 함수(2)가 호출될 경우 함수(1) 내부 코드가 끝나야지만 함수(2)가 호출되는 원리를 이용한다.

function requestData1 (callback) {
 	var data = fetch(url)
  	callback(data)
}

requestData1(function callback(data) {
  console.log(data);
});

위가 콜백함수를 사용해서 data를 받아온 이후에 callback 이 실행되는 예이다.

콜백함수는 코드 흐름이 뒤죽박죽이라 코드가 읽기 힘들고 여러번의 콜백함수가 중첩될 경우 콜백 지옥이라 불리는 현상이 일어난다.

프로미스

es6 이후 자바스크립트 문법은 이 콜백 문제를 promise를 통해 해결했다.

프로미스는 비동기 상태를 값으로 다룰 수 있는 객체이다. 즉 비동기 처리에 사용되는 객체이다.

프로미스를 사용하면 비동기 코드를 순차적으로 작성할 수 있는 장점이 있다.

function getData(callbackFunc) {
  $.get('url 주소/products/1', function(response) {
    callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
  });
}

getData(function(tableData) {
  console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});

위 코드는 간단하게 콜백함수를 통해 비동기 처리를 한 내용이다.

위 내용에 프로미스를 적용하면

function getData(callback) {
  // new Promise() 추가
  return new Promise(function(resolve, reject) {
    $.get('url 주소/products/1', function(response) {
      // 데이터를 받으면 resolve() 호출
      resolve(response);
    });
  });
}

// getData()의 실행이 끝나면 호출되는 then()
getData().then(function(tableData) {
  // resolve()의 결과 값이 여기로 전달됨
  console.log(tableData); // $.get()의 reponse 값이 tableData에 전달됨
});

가 된다. 함수에서 프로미스 객체를 리턴함으로 resolve 함수를 통해 성공했을 때 실행할 함수, reject 함수를 통해 비동기 처리에 실패했을 때 실행할 함수를 구분지을 수 있다.

프로미스 체이닝

프로미스가 콜백보다 확연히 가독성에서 차이가 나는 점은 여러 비동기 함수가 중첩되었을 경우이다.

콜백 지옥이라고 불리는 이 함수 체이닝에서 프로미스는 확연하게 가독성에서 좋다.

$.get('url', function(response) {
	parseValue(response, function(id) {
		auth(id, function(result) {
			display(result, function(text) {
				console.log(text);
			});
		});
	});
});

위는 콜백 지옥을 나타낸 예시이다.

getData(userInfo)
  .then(parseValue)
  .then(auth)
  .then(diaplay);

function parseValue() {
  return new Promise({
    // ...
  });
}
function auth() {
  return new Promise({
    // ...
  });
}
function display() {
  return new Promise({
    // ...
  });
}

프로미스를 통해 위처럼 코드를 개선할 수 있다.

한 눈에 봐도 가독성이 높아진 코드를 볼 수 있다.

async / await

프로미스의 then 체이닝 형식의 가독성을 조금 더 보완하기 위해 async await 키워드가 도입되었다. 하지만 이 개념은 프로미스를 완벽하게 커버하는 업그레이드 버전이 아니다. 프로미스의 부족한 부분을 보완하는 기능을 한다고 생각하면 될 것 같다.

async/await 는 함수에 적용되는 개념으로 일반 함수에 적용이 되면 프로미스 객체를 반환하게 된다.

async function getData(){
  return 123;
}
getData().then(data => console.log(data));
console.log(getData());

async를 적용하는 함수를 만들면 프로미스 객체를 리턴하는 함수로 바뀐다. getData 함수는 프로미스를 리턴해 then() 을 사용해 콜백함수를 실행하고 있다.

아래 콘솔로그는 "Promise { 123 }" 라는 결과가 나온다.

function requestData(value){
  return new Promise(resolve => setTimeout(()=>{
   console.log("requestData" , value);
    resolve(value);
  },100);
 );
};

async function getData(){
 const data1 = await requestData(10);
 const data2 = await requestData(20);
  console.log(data1, data2);
  return [data1, data2];
  
}

console.log(getData());

위 처럼 실행하면

Promise { <pending> }
requestData 10
requestData 20
10 20

가 나온다.

pending 은 대기중이라는 뜻. 결과값을 기다리고 있는 중이라는 뜻이다.

async 함수 자체는 promise 객체를 반환하고, 그 결과값을 then()을 통해 처리할 수 있다.

나머지는 setTimeout함수가 실행됨에도 불구하고, data1, data2에 undefined가 아닌, 정상적인 값이 들어가 출력된다. 비동기처리를 async await가 한 것이다.

async/await 보다 프로미스 형태로 실행하는 것이 효과적인 경우

async await 은 의존성이 있는 체인형태의 비동기 코드를 다룰 때 효과적이다. 즉 연쇄적으로 이전 코드의 데이터를 필요로 해 대기해야 하는 경우 async await은 코드의 가독성을 효과적으로 높여준다.

하지만 이런 특성으로 인해, 병렬 처리를 할 수 있는 즉, 의존성이 없는 비동기 코드를 실행할 때는 효과가 떨어진다.

동시에 실행해도 되는 코드를 굳이 대기해야 하니 효율성이 떨어지는 것이다.

이럴 경우에는 두 가지 방법을 사용해서 병렬 처리를 할 수 있다.

  async function getData(){
  	const p1 = asyncFunc1();(프로미스 객체 리턴 함수)
  	const p2 = asyncFunc2(); 
  
  	const data1 = await p1;
  	const data2 = await p2;
  	const data3 = await asyncFunc3(data1, data2);
  }
  

위 코드 처럼 프로미스를 먼저 생성함으로써 비동기 코드를 실행하는 방법을 통해 병렬처리가 가능하다.

async function getData(){
	const [data1, data2] = await Promise.all([asyncFunc1(), asyncFunc2()])
}

처럼 병렬처리를 할 수도 있다.

비동기 처리는 네트워크를 다루는 개발자라면 반드시 숙지해야 할 내용이고 면접에서도 단골로 나오는 내용이니 꼭 숙지해야 한다.

profile
성장하는 개발자

0개의 댓글