thumbnail

자바스크립트에서 비동기 처리하는 방법 3가지

생성일2024. 10. 18.
태그
작성자Beomgu Jeon

자바스크립트에서 비동기 처리 하는 방법 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', () => {}); }); });
하지만 위 코드를 보면 한 눈에 보기에도 읽기 어렵고 가독성이 떨어진다. 아래는 콜백 지옥과 관련된 재미있는 이미지이다.
notion image
이 처럼 콜백함수가 많아지면 많아질수록 코드의 가독성이 정말 많이 떨어진다. 이를 좀 더 직관적으로 보기 위해 나온 것이 바로 이제 살펴볼 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 의 사용은 지양하는 편이 좋아보인다.