프론트엔드 개발에서 비동기 처리는 자주 사용되는 패턴입니다. 자바스크립트는 비동기 처리를 지원하기 위해 콜백, 프로미스, async/await와 같은 다양한 방법을 제공합니다. 하지만 이러한 비동기 처리 방식은 개발자들 사이에서 여러 오해를 불러일으키고 있습니다. 이번 글에서는 자바스크립트 비동기 처리에서 발생할 수 있는 오해와 그 해결법에 대해 알아보겠습니다.
1. 콜백 지옥
가장 흔한 오해 중 하나는 콜백을 중첩해서 사용할 경우 코드가 복잡해지고 가독성이 떨어진다는 것입니다. 이를 종종 '콜백 지옥'이라고 부릅니다. 다음은 콜백 지옥의 간단한 예입니다.
function firstTask(callback) {
setTimeout(() => {
console.log("첫 번째 작업 완료");
callback();
}, 1000);
}
function secondTask(callback) {
setTimeout(() => {
console.log("두 번째 작업 완료");
callback();
}, 1000);
}
function thirdTask() {
setTimeout(() => {
console.log("세 번째 작업 완료");
}, 1000);
}
firstTask(() => {
secondTask(() => {
thirdTask();
});
});
이 코드에서 firstTask, secondTask, thirdTask를 콜백으로 서로 연결하면서 가독성이 떨어지는 것을 볼 수 있습니다. 이 문제를 해결하기 위해 프로미스를 사용할 수 있습니다.
2. 프로미스의 이해 부족
프로미스는 비동기 작업의 완료(혹은 실패)를 나타내는 객체입니다. 그러나 프로미스를 사용할 때도 잘못 이해하고 사용하는 경우가 많습니다. 예를 들어, 프로미스가 항상 성공적으로 완료된다고 생각할 수 있지만, 실제로는 실패할 수도 있습니다.
const task = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5; // 50% 확률로 성공
if (success) {
resolve("작업 성공");
} else {
reject("작업 실패");
}
}, 1000);
});
};
task()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
위의 코드에서 task 함수는 50% 확률로 성공하거나 실패합니다. 프로미스를 사용할 때는 항상 catch를 사용해 에러를 처리하는 것이 중요합니다.
3. async/await의 사용
async/await는 비동기 코드를 더 간결하고 읽기 쉽게 만들어줍니다. 대부분의 개발자들이 async/await을 사용하면서도 여전히 비동기 처리에 대한 기본 개념을 간과하는 경우가 많습니다. async 함수는 항상 프로미스를 반환하며, 그 결과를 기다리기 위해 await를 사용할 수 있습니다.
const asyncTask = async () => {
try {
const result = await task();
console.log(result);
} catch (error) {
console.error(error);
}
};
asyncTask();
위의 예제에서 asyncTask 함수는 async로 정의되었고, 내부에서 await를 사용하여 task 함수의 결과를 기다립니다. 에러가 발생할 가능성이 있기 때문에 try/catch를 사용하여 적절히 처리하고 있습니다.
4. 비동기 처리를 위한 병렬 처리
비동기 작업이 여러 개 있을 때, 이를 병렬로 처리하고 싶을 때가 많습니다. Promise.all()을 사용하면 여러 프로미스를 동시에 실행할 수 있습니다. 하지만 모든 프로미스가 성공해야 결과를 반환하므로, 각 프로미스의 성공 여부에 따라 다른 처리를 하고 싶다면 Promise.allSettled()를 사용하는 것이 좋습니다.
const task1 = () => new Promise(resolve => setTimeout(() => resolve("작업 1 완료"), 1000));
const task2 = () => new Promise((_, reject) => setTimeout(() => reject("작업 2 실패"), 500));
const task3 = () => new Promise(resolve => setTimeout(() => resolve("작업 3 완료"), 1500));
Promise.allSettled([task1(), task2(), task3()])
.then(results => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(작업 ${index + 1} 성공: ${result.value});
} else {
console.error(작업 ${index + 1} 실패: ${result.reason});
}
});
});
위의 코드에서는 Promise.allSettled를 사용하여 모든 비동기 작업의 결과를 확인합니다. 각각의 작업이 성공했는지 실패했는지를 확인할 수 있어 유용합니다.
마무리
비동기 처리는 프론트엔드 개발에서 필수적인 기술입니다. 그러나 이와 관련된 다양한 오해는 개발 과정에서 혼란을 초래할 수 있습니다. 콜백 지옥, 프로미스의 이해 부족, async/await의 잘못된 사용, 비동기 작업의 병렬 처리에 대한 이해 부족 등은 모두 비동기 처리에서 발생할 수 있는 대표적인 오해들입니다. 이 글을 통해 비동기 처리에 대한 보다 명확한 이해와 올바른 사용법을 습득하시길 바랍니다.