자바스크립트에서 비동기 제어 흐름(Asynchronous Control Flow)은 비동기 작업을 효과적으로 관리하고 순서대로 실행하기 위한 다양한 방법과 패턴을 의미합니다. 비동기 제어 흐름을 잘 이해하면 복잡한 비동기 작업을 더 쉽게 관리하고 예기치 않은 동작을 방지할 수 있습니다. 여기서는 콜백, 프라미스, async/await 및 제어 흐름 라이브러리를 통해 비동기 제어 흐름을 관리하는 방법에 대해 상세히 설명하겠습니다.
1. 콜백 (Callback)
콜백 함수는 비동기 작업이 완료되면 호출되는 함수입니다. 콜백은 가장 기본적인 비동기 제어 흐름 관리 방법입니다.
1.1 콜백 패턴의 문제점
콜백 지옥(Callback Hell)이라 불리는 문제는 중첩된 콜백으로 인해 코드가 복잡하고 가독성이 떨어지는 상황을 말합니다. 예를 들어:
function doTask1(callback) {
setTimeout(() => {
console.log('Task 1 completed');
callback();
}, 1000);
}
function doTask2(callback) {
setTimeout(() => {
console.log('Task 2 completed');
callback();
}, 1000);
}
function doTask3(callback) {
setTimeout(() => {
console.log('Task 3 completed');
callback();
}, 1000);
}
doTask1(() => {
doTask2(() => {
doTask3(() => {
console.log('All tasks completed');
});
});
});
- 이 예제는 단순하지만, 더 많은 비동기 작업이 중첩될수록 코드가 복잡해집니다.
2. 프라미스 (Promise)
프라미스는 콜백보다 더 간단하고 강력한 비동기 제어 흐름 관리 방법을 제공합니다.
2.1 프라미스 체이닝
프라미스 체이닝을 통해 비동기 작업을 순차적으로 실행할 수 있습니다.
function doTask1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 1 completed');
resolve();
}, 1000);
});
}
function doTask2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 2 completed');
resolve();
}, 1000);
});
}
function doTask3() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task 3 completed');
resolve();
}, 1000);
});
}
doTask1()
.then(doTask2)
.then(doTask3)
.then(() => {
console.log('All tasks completed');
});
- 프라미스 체이닝을 사용하면 중첩된 콜백 대신 순차적인 코드 흐름을 유지할 수 있습니다.
2.2 프라미스 병렬 실행
여러 비동기 작업을 병렬로 실행할 때는 Promise.all
을 사용합니다.
Promise.all([doTask1(), doTask2(), doTask3()])
.then(() => {
console.log('All tasks completed');
});
Promise.all
은 모든 프라미스가 완료될 때까지 기다리고, 모든 작업이 완료되면.then
블록이 실행됩니다.
3. Async/Await
Async/Await는 프라미스를 기반으로 비동기 코드를 동기 코드처럼 작성할 수 있게 합니다.
3.1 기본 Async/Await 예제
async function performTasks() {
await doTask1();
await doTask2();
await doTask3();
console.log('All tasks completed');
}
performTasks();
await
키워드는 프라미스가 해결될 때까지 기다리며, 그동안 다른 작업을 블로킹하지 않습니다.performTasks
함수는async
키워드로 선언되어 비동기 함수가 됩니다.
3.2 Async/Await와 병렬 실행
Async/Await를 사용하여 병렬로 작업을 실행하려면 Promise.all
을 함께 사용합니다.
async function performTasksInParallel() {
await Promise.all([doTask1(), doTask2(), doTask3()]);
console.log('All tasks completed');
}
performTasksInParallel();
- 이렇게 하면 세 작업이 병렬로 실행되며, 모두 완료될 때까지 기다립니다.
4. 제어 흐름 라이브러리
제어 흐름 라이브러리는 복잡한 비동기 제어 흐름을 단순화하고 관리하기 위해 사용됩니다. 대표적인 라이브러리로는 async
가 있습니다.
4.1 async
라이브러리
async
라이브러리는 Node.js 환경에서 비동기 작업을 쉽게 관리할 수 있는 유틸리티 함수를 제공합니다.
const async = require('async');
async.series([
function(callback) {
setTimeout(() => {
console.log('Task 1 completed');
callback(null, 'Task 1 result');
}, 1000);
},
function(callback) {
setTimeout(() => {
console.log('Task 2 completed');
callback(null, 'Task 2 result');
}, 1000);
},
function(callback) {
setTimeout(() => {
console.log('Task 3 completed');
callback(null, 'Task 3 result');
}, 1000);
}
], function(err, results) {
if (err) {
console.error(err);
return;
}
console.log('All tasks completed, results:', results);
});
async.series
는 배열에 정의된 작업을 순차적으로 실행하며, 모든 작업이 완료되면 최종 콜백이 호출됩니다.async.parallel
이나async.waterfall
등의 다른 메서드를 사용하여 병렬 실행이나 순차 실행과 결과 전달을 쉽게 관리할 수 있습니다.
5. 예외 처리
비동기 제어 흐름에서 예외 처리는 매우 중요합니다. 프라미스와 async/await는 예외 처리를 단순화합니다.
5.1 프라미스의 예외 처리
doTask1()
.then(doTask2)
.then(doTask3)
.then(() => {
console.log('All tasks completed');
})
.catch((error) => {
console.error('Error occurred:', error);
});
- 프라미스 체이닝의 마지막에
.catch
를 사용하여 에러를 처리합니다.
5.2 Async/Await의 예외 처리
async function performTasks() {
try {
await doTask1();
await doTask2();
await doTask3();
console.log('All tasks completed');
} catch (error) {
console.error('Error occurred:', error);
}
}
performTasks();
try...catch
블록을 사용하여 async 함수 내에서 발생한 에러를 처리합니다.
결론
자바스크립트의 비동기 제어 흐름을 이해하고 잘 사용하는 것은 복잡한 비동기 작업을 효과적으로 관리하고 코드의 가독성을 높이는 데 필수적입니다. 콜백, 프라미스, async/await, 제어 흐름 라이브러리를 활용하여 비동기 작업을 순차적 또는 병렬로 실행하고, 예외를 효과적으로 처리할 수 있습니다. 이를 통해 보다 견고하고 유지보수하기 쉬운 애플리케이션을 개발할 수 있습니다.