본문 바로가기

Web

[JavaScript] 비동기 처리 asynchronous programming

동기 vs 비동기


동기는 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 합니다. 비동기는 요청과 결과가 동시에 일어나지 않아도 됩니다. 즉, 요청에 대한 실행이 완료되어 결과가 나올 때까지 기다리지 않고 다음 요청을 먼저 수행하는 것입니다. 예를 들면 카페에서 주문을 하는 상황에서 동기적인 시스템이라면 커피를 주문하고 주문한 커피를 받을 때까지 카운터 앞에서 자리를 지켜야 합니다. 반면 비동기적인 시스템에서는 주문을 하면 진동벨을 받은 후, 장소를 옮기거나 다른 일을 할 수 있습니다. 그리고 주문한 커피가 나오면 진동벨이 울리고 주문자는 커피가 나왔음을 알 수 있습니다.

 

동기 처리방식은 설계가 매우 간단하고 직관적이지만 결과가 주어질 때까지 아무것도 못하고 대기해야 하는 (블록 상태) 단점이 있습니다. 비동기 처리방식은 동기보다 복잡하지만 결과가 주어지는데 걸리는 시간동안 다른 작업을 할 수 있기에 (논블록 상태) 자원을 효율적으로 사용할 수 있다는 장점이 있습니다.

비동기 처리 방법


1. Callback

콜백은 자바스크립트에 존재하는 가장 고전적인 비동기 프로그래밍 방식입니다. 콜백함수를 인자로 받는 함수, 즉 고차함수의 형태를 하고 있으며 지정한 타이밍이 되면 콜백함수가 실행됩니다.

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');
// 1, 3, 2

Callback Hell (콜백 지옥)

콜백 지옥은 비동기 처리를 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제입니다. 콜백 안에 콜백을 계속 반복하는 형식으로 프로그래밍을 하게 되는데, 이렇게 코드를 짜면 가독성이 떨어지고 로직을 변경하기 어렵습니다.

콜백 지옥을 해결하기 위한 방법이 다음에 등장하는 Promise와 async/await 방법입니다.

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

2. Promise

Promise는 비동기 처리에 사용되는 객체입니다. Promise는 처리 과정에서 3가지 상태를 갖습니다.

  • pending(대기) -> 비동기 처리 로직이 완료되지 않은 상태
  • fulfilled(이행) -> resolve가 호출되어 값을 넘길 수 있는 상태, then 사용 가능 상태, 비동기 처리가 완료되어 promise가 결과값을 반환해준 상태
  • rejected(실패)  -> reject가 호출되어 값을 넘길 수 없는 상태, catch 사용 가능 상태, 비동기 처리가 실패하거나 오류가 발생한 상태
function getData() {
	return new Promise(function(resolve, reject) {
    // ...
}

getData().then(function(data) {
	console.log(data);
}).catch(function(err) {
	console.log(err);
});

 

비동기를 객체값으로 다루면 비동기 제어를 보다 쉽게 할 수 있고, Promise.all 메서드를 사용하면 병렬처리를 할 수 있습니다.

const p1 = new Promise(resolve => {
  setTimeout(() => resolve('resolve: p1'), 3000);
})
const p2 = new Promise(resolve => {
  setTimeout(() => resolve('resolve: p2'), 5000);
});

Promise.all([p1, p2]).then(([r1, r2]) => {
  console.log(r1, r2);
});

 

또한 프로미스는 여러 개를 연결하여 chaining 하여 사용할 수 있다는 특징이 있습니다.

new Promise(function(resolve,reject) {
  setTimeout(function() {
    resolve(1);
  }, 2000);
})
.then(function(result) {
  console.log(result); // 1
  return result + 10; 
})
.then(function(result) {
  console.log(result); // 11
  return result + 20;
})
.then(function(result) {
  console.log(result); // 31
});

3. async / await

Promise 방식에서 then chaining이 많아지면 가독성이 떨어진다는 단점을 보완하면서 코드를 위에서부터 아래로 한줄씩 보기 좋게 프로그래밍하기 위해 async / await 구문이 등장하였습니다.

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

 

이때 비동기 처리 메서드가 프로미스 객체를 반환해야 await가 의도한 대로 동작합니다.

async & 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]
}

 

async & await 비동기 방식에서 예외를 처리하기 위해서는 try, catch를 사용합니다.

async function todo() {
	try {
    	var user = await fetchUser();
        // ...
    } catch (error) {
    	console.log(error);
    }
}

 

 

 

 

 

참고:

 

https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/

https://velog.io/@thsoon/JS-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B5%AC%ED%98%84%EB%90%98%EC%96%B4%EC%9E%88%EB%8A%94%EA%B0%80

https://intzzzero.netlify.app/blog/why-using-async