# Javascript 비동기 처리에 대한 정리

이 글에서는 자바스크립트의 비동기 처리 수단 세가지에 대해 알아보고, 각자가 어떤 장단점과 한계점을 가졌는지 정리합니다.

## 세 줄 요약

* **자바스크립트 비동기 처리:** 자바스크립트는 비동기 처리를 위해 콜백, 프로미스, async/await를 제공합니다. 콜백은 간단하지만 "콜백 지옥"을 유발할 수 있고, 프로미스는 이를 개선했지만 복잡할 수 있으며, async/await는 가독성이 좋지만 제어권 상실 및 자원 누수 위험이 있습니다.
    
* **프로미스와 async/await의 문제:** async/await 사용 시 비동기 작업 완료까지 함수 실행이 중단되어 제어권 상실 및 자원 누수가 발생할 수 있으며, 프로미스 처리 여부도 보장되지 않습니다.
    
* **주의 사항:** 각 방식은 "콜백 지옥"이나 "await 사건의 지평선" 같은 단점이 있으므로, 이를 이해하고 상황에 맞는 비동기 처리 방식을 선택하는 것이 중요합니다.
    

# 세 가지 비동기 처리 방식 정리 및 장단점

### 1\. 콜백(Callback) 함수

* **특징**: 가장 기본적인 비동기 처리 방식. 함수를 다른 함수의 인자로 넘겨주고, 어떤 이벤트가 발생한 후 해당 함수가 나중에 호출됩니다.
    
* **장점**: 간단한 비동기 처리, 함수 주입을 통한 제어 역전, 오래된 라이브러리/프레임워크와 호환 용이, 낮은 학습 곡선이 있습니다.
    
* **단점**: 중첩된 콜백으로 인해 코드의 복잡도가 증가하는 '콜백 지옥' 이 발생 할 수 있습니다.
    
* **사용 예시**:
    

```javascript
function fetchData(callback) {
    setTimeout(() => {
        callback('데이터');
    }, 1000);
}

fetchData(data => {
    console.log(data); // '데이터'
});
```

### 2\. 프로미스(Promise)

* **특징**: 콜백 지옥의 해결책으로 등장했습니다. 비동기 작업의 최종 성공 또는 실패를 나타내는 객체입니다.
    
* **장점**: 연속된 비동기 작업을 체이닝을 통해 깔끔하게 표현 가능합니다. 작업 체인 내에서의 오류를 `catch()`를 통해 쉽게 제어 할 수 있고, [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) 등을 활용해서 병렬처리도 지원합니다.
    
* **단점**: 잘못 사용하면 여전히 콜백 지옥을 유발할 수 있습니다.
    
* **사용 예시**:
    

```javascript
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('데이터');
        }, 1000);
    });
}

fetchData().then(data => {
    console.log(data); // '데이터'
}).catch(error => {
    console.error(error);
});
```

### 3\. `async/await`

* **특징**: 프로미스를 기반으로 한 비동기 처리 패턴입니다. Promise의 활용할 시의 보일러플레이트 코드를 입력 안해도 되게 해 주는 문법적 설탕(Syntactic sugar)입니다.
    
* **장점**: 코드의 가독성을 대폭 향상 해줍니다. `try/catch` 블록을 사용한 에러 처리가 가능해서 좀 더 익숙한 방식의 에러 처리가 가능합니다.
    
* **단점**:
    
* **사용 예시**:
    

```javascript
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

fetchData();
```

여기서 볼 수 있듯이, 콜백 -&gt; 프로미스 -&gt; `async/await`로 넘어오면서 비동기 처리의 복잡성과 가독성 문제가 점점 개선되고 있습니다. `async/await`는 내부적으로 프로미스를 사용하긴 하지만, 코드를 동기식으로 보이게 하여 더 이해하기 쉽고, 관리하기 편리한 형태로 작성할 수 있게 해줍니다.

## Promise와 `async/await`는 그럼 무조건 좋을까요?

답은 당연히 그렇지 않습니다. 지금은 많이 알려진 심각한 단점이 있습니다.

### **제어권의 상실**

* `async/await`를 사용하면, `await` 키워드가 사용된 비동기 작업이 완료될 때까지 함수의 실행이 멈추게 됩니다. 이는 ‘작업이 완료되지 않으면 함수가 영원히 중단될 수 있음’을 의미합니다. 즉, 프로그램의 나머지 부분으로 제어권이 돌아가지 않을 수 있습니다.
    

### 자원 누수의 위험

* 메모리라는 제한된 자원의 누수가 축적될 수 있는 위험이 존재합니다. 하단의 예시에서는 간단한 락(lock) 메커니즘을 사용하여, `work` 함수의 실행 전에 락을 획득하고 실행 후에 락을 해제하는 방식으로 자원 관리를 시도해보겠습니다.
    

```typescript
async function acquireLock() {
    // 임의의 락 획득 로직을 가정
    console.log("Lock acquired");
    return { id: "lockId" }; // 실제 구현에서는 락 객체를 반환할 것
}

function releaseLock(lock) {
    // 임의의 락 해제 로직을 가정
    console.log(`Lock ${lock.id} released`);
}

async function protect(work) {
    let lock = await acquireLock();
    try {
        await work();
    } finally {
        releaseLock(lock);
    }
}

function work() {
    return new Promise((resolve, reject) => {
        // 임의의 비동기 작업을 가정
        setTimeout(() => {
            console.log("Work done"); // <- ⚠️ 여기서 에러가 발생하면?
            resolve();
        }, 2000); // 2초 후 작업 완료
    });
}

protect(work);
```

* 예로 들어진 `protect` 함수에서 볼 수 있듯이, `work()`에서 반환된 `Promise`가 임의의 문제로 resolve 되지 않으면, `protect()` 함수는 "await 사건의 지평선"을 넘어서고 재개되지 않게 됩니다. 이는 초기에 획득한 자원이 영원히 해제되지 않음을 의미하며, 이러한 자원 누수는 시스템에 심각한 문제를 일으킬 수 있습니다.
    

### 비동기 작업 처리에 대한 보장이 없음

* `Promise`가 언제 처리될지, 심지어 처리될지 여부조차도 보장할 수 없습니다. 이는 코드가 필요한 설정, 동작 수행, 그리고 해제 작업을 안전하게 완료하는 것을 어렵게 만듭니다.
    

**그래서 이 문제를 해결하려면 어떻게 해야할까요?** 내용이 너무 길어질 수 있어서 좋은 글 링크를 소개해 드리겠습니다. [자바스크립트 await 사건의 지평선](https://velog.io/@sehyunny/await-event-horizon)

이러한 Promise와 async/await에 있는 문제들은 콜백에는 해당되지 않는 문제입니다. 콜백은 비동기 작업이 완료될 때 실행되는 함수를 제공하지만, `Promise`나 `async/await`처럼 실행 흐름을 멈추거나 "await 사건의 지평선" 같은 개념에 직접적으로 묶여 있지 않습니다.

하지만 콜백을 사용할 때도 비동기 작업의 완료를 관리하는 복잡성, 에러 처리의 어려움 등 다른 종류의 문제가 발생할 수 있으므로 여전히 좋은 선택지는 아닙니다.

## 마치며

위에서 소개 드린 것처럼 콜백(Callback), 프로미스(Promise), async/await는 비동기 작업을 처리하는 방법들이며, 각기 다른 장단점을 가지고 있습니다. 콜백은 가장 기본적이지만 콜백 지옥으로 인한 복잡성이 문제가 될 수 있고, 프로미스와 async/await는 가독성과 에러 처리를 개선하지만, 제어권 상실과 자원 누수의 위험이 있습니다. 이들 방법은 비동기 처리를 용이하게 하지만, 각각의 사용 시 주의가 필요합니다.
