자바스크립트에서 비동기 처리 하는 방법 3가지
동기 vs 비동기
자바스크립트에서 비동기 처리하는 방법을 알아보기 전에 먼저 동기와 비동기가 무엇인지부터 알아보자.
동기 (Synchronous)
동기적이라는 의미는 한 작업의 실행이 끝날 때까지는 다른 작업을 할 수 없고, 진행중이던 작업이 모두 끝난 후에 다른 작업을 진행할 수 있는 것을 의미한다. 이렇게 동기적인 것은 다른 말로는
blocking
하다고 말한다. 한 작업이 끝날 때 까지는 다른 작업이 불가하기 때문이다.비동기 (Asynchronous)
비동기적의 의미는 동기와 반대로 한 작업의 실행이 끝나지 않아도 다른 작업이 가능하다는 의미이다. 원래 진행중이던 작업이 끝나지 않아도, 작업을 요청하면 새로 요청한 작업을 시작할 수 있다는 의미이다. 그래서 비동기는
non-blocking
하다고 말한다.자바스크립트에서의 동기와 비동기
기본적을 자바스크립트는 기본적으로 동기식 언어로, 한 작업의 실행이 끝날 때까지는 다른 작업을 할 수 없다. 하지만 자바스크립트에서도 비동기적으로 요청을 처리 할 수 있는 방법이 있는데, 오늘은 어떻게 비동기적으로 요청을 처리하는 지에 대해 알아보고자 한다.
자바스크립트에서 비동기로 처리하는 방법은 크게 아래 세 가지가 있다.
- Callback Function
- Promise
- Async / Await
이제부터 이를 차근차근 알아보도록 하자.
Callback Function
자바스크립트에서 대표적인 비동기 방식인
setTimeout
함수를 통해 알아보도록 하자.const print = str => { setTimeout(() => { console.log(str); }, Math.floor(Math.random() * 1000) + 500); }; print('First'); print('Second'); print('Third');
먼저 위의 작성된 예제 코드를 실행히보면, 겉으로 보기에는
First, Second, Third
의 순서로 실행될 거 같지만, 그렇지 않다. print 함수의 경우 setTimeout
함수를 이용하여 일정 시간 대기 후 출력을 해주는 데, 이 때 사용된 setTimeout
함수가 바로 비동기 적으로 처리하기 때문이다. 그렇다면 이를 순서대로 출력하고 싶다면 어떻게 해야할까?이를 해결하기 위한 방법 중 첫 번째가 바로
Callback Function
을 이용하는 것이다. 그렇다면 Callback Function
이 무엇이냐? 바로 함수의 인자로 자신이 원하는 함수를 전달하고, 원하는 시점에 전달받은 함수를 실행시켜주는 방식을 말한다. 이 말은 즉, 전달받은 인자(함수)를 바로 실행할 수도, 비동기 작업이 끝나고 실행할 수도 있다는 것이다.// 콜백 함수 전달 받아 실행 const func = callback => { callback(); }; // 인자 없는 콜백 함수 const callbackWithNoArgs = () => { console.log('This is callback function! - no args'); }; // 인자 있는 콜백 함수 const callbackWithOneArg = str => { console.log(`This is callback function! - ${str}`); }; // 인자 없는 콜백함수 전달 func(callbackWithNoArgs); // 인자 있는 콜백함수 전달 func(() => { callbackWithOneArg('test'); });
위 코드는 콜백 함수를 전달받아 바로 실행시켜주는 예제이다.
그렇다면, 처음에 이야기한 함수를 순서대로 출력하기 위해서는 어떻게 해야할까? 위를 참고하여 아래와 같이 코드를 짤 수 있다.
const print = (str, callback) => { setTimeout(() => { console.log(str); callback(); }, Math.floor(Math.random() * 1000) + 500); }; print('First', () => { print('Second', () => { print('Third', () => {}); }); });
하지만 위 코드를 보면 한 눈에 보기에도 읽기 어렵고 가독성이 떨어진다. 아래는 콜백 지옥과 관련된 재미있는 이미지이다.

이 처럼 콜백함수가 많아지면 많아질수록 코드의 가독성이 정말 많이 떨어진다. 이를 좀 더 직관적으로 보기 위해 나온 것이 바로 이제 살펴볼
Promise
문법이다.Promise
Promise 는 일종의 콜백 함수를 관리하기 위한 하나의 객체이다. 콜백 함수를 체이닝(chaining) 기법으로 좀 더 직관적으로 볼 수 있게 해준다. Promise 객체는 아래의 3가지 상태를 가진다.
pending
: 초기 상태
Promise
객체가 처음 생성되고 초기 상태를 의미한다.fulfilled
: 프로세스 처리가 완료됨
Promise
객체에서 모든 프로세스 처리가 완료됨을 의미하고, resolve()
메소드를 사용하여 원하는 리턴 값을 전달할 수 있다.rejected
: 프로세스 처리가 실패함
Promise
객체에서 프로세스 처리가 실패했음을 의미하고, reject()
메소드를 사용하여 원하는 리턴 값을 전달할 수 있다.아래 예제에서는 위에서 콜백 함수를 이용하여 순차적으로 프린트 하는 로직을 아래와 같이 바꿔보았다.
reject()
메소드의 사용법도 보여주기 위해, print
함수에 한 가지 로직을 더 추가하였다. 바로 랜덤으로 숫자를 생성 후 홀수인 경우 reject()
메소드로 실패를 반환하는 것이다.const print = str => { return new Promise((resolve, reject) => { setTimeout(() => { // 추가된 부분 const val = Math.floor(Math.random() * 10 + 1); if (val % 2 === 0) { console.log(str); resolve(); } else { reject('The random number is odd!'); // 실패 메시지 반환 } }, Math.floor(Math.random() * 1000) + 500); }); }; print('First') .then(() => { return print('Second'); }) .then(() => { return print('Third'); }) .catch(err => { console.log(err); });
위 코드를 살펴보면 처음으로
First
를 프린트하고, 성공하면 Second
, Third
를 차례로 프린트 한다. 다만, 여기서 주의할 점은 중간에 랜덤 값이 홀수가 나와 reject()
가 반환되면 이후에 있는 then()
블록은 실행되지 않고 바로 catch
블록으로 빠지는 점이다. Promise
를 사용할 때는 이를 꼭 기억하면서 프로그래밍을 하도록 하자.Async / Await
하지만
Promise
도 마찬가지로 콜백 함수가 많이 중첩될수록 then()
지옥에 빠질 수 있다. 그래서 현재 최종적으로 나온 것이 바로 async / await
키워드이다.async
키워드는 함수 바로 앞에 붙여서 이 함수는 비동기 로직을 처리하는 함수임을 명시할 수 있다.await
키워드는 위의 async
키워드를 이용하여 정의한 비동기 함수 내에서만 사용할 수 있으며, Promise
객체를 생성하는 라인의 앞에 놓이고, Promise
객체의 실행이 끝날 때 까지 기다린다. 위의 예제를 async / await
키워드를 이용하면 아래와 같이 작성할 수 있다.const print = str => { return new Promise((resolve, reject) => { setTimeout(() => { const val = Math.floor(Math.random() * 10 + 1); if (val % 2 === 0) { console.log(str); resolve(); } else { reject('The random number is odd!'); } }, Math.floor(Math.random() * 1000) + 500); }); }; const printAll = async () => { try { await print('First'); await print('Second'); await print('Third'); } catch (e) { console.log(e); } }; printAll();
다만 유의할 점은,
Promise
객체를 사용하여 체이닝 기법으로 관리할 때는 성공하면 then()
블록에서 처리를, 실패하면 catch()
블록에서 처리를 해주었다. 하지만 async / await
를 사용하게 되면 체이닝 기법을 사용하지 않으므로, 예외 처리를 할 때 try-catch
블록을 활용하여 예외 처리를 해주어야한다.결론
자바스크립트에서 비동기 프로그래밍을 할 수 있는 방법 세 가지를 알아보았는데, 가급적이면 가독성이 좋은
Promise
, async / await
문법을 적절하게 사용하는 것을 추천하고, 꼭 필요한 상황이 아니면 callback function
의 사용은 지양하는 편이 좋아보인다.