서론
이번에 React를 공부하면서 공부해야될 개념이 있었다.
자바스크립트 에서 비동기 처리 방식의 종류와 각 역할에 따라 언제 사용해야 좋을지에 대해서다.
강의를 들으면서 어떨때는 Callback 또 어떨때는 Promise 또 어떨때는 async/await을 사용하는 것이다.
Flutter 를 사용하면서는 Future, async, await, Stream 만 사용해왔었는데 JavaScript 에서의 비동기 처리를 하는 방법들이 각각 어떤 상황에서 사용해야 효율적인 프로그래밍이 될지에 대해서 알아볼 필요가 있다고 생각해서 이에 대해 정리해보려고 한다.
- 비동기 프로그래밍에 대한 예시
2025.01.31 - [📱Flutter] - Flutter 에서의 비동기 프로그래밍 Future, async/await
Flutter 에서의 비동기 프로그래밍 Future, async/await
맨 처음 iOS를 공부하면서 가장 어렵게 느껴졌던 개념이 비동기 프로그래밍이었다.내용만 듣고 이걸 왜 쓰는 거지?라는 의문이 들어서 대충 흘려보냈다가 프로젝트에 들어가면서 그 중요성에 대
sj-d.tistory.com
자바스크립트의 비동기 처리
자바스크립트에서 비동기 처리는 웹 개발에서의 필수적인 요소이다.
서버에서 데이터를 가져오거나, 파일을 읽거나, 타이머를 설정하는 등의 작업은 모두 비동기적으로 처리하게 된다. 이렇게 비동기 작업을 효율적으로 다루기 위해서 자바스크립트에서는 세가지 주요 방식 (Callback, Promise, async/await)을 제공한다.
그리고 자바스크립트는 싱글 스레드 언어로 한번에 하나의 작업만 수행할 수 있다. 하지만 웹 애플리케이션은 여러 작업을 동시에 처리해야 하는 경우가 많다. 예를들어 사용자의 입력을 받으면서 동시에 서버에서 데이터를 가져와야 할 때 비동기 처리가 필요하게 된다. 이러한 비동기 처리 작업을 효율적으로 관리하기 위해 Callback, Promise, async/awit이 등장했다.
Callback(콜백 함수)
기본 개념
콜백 함수는 다른 함수에 인자로 전달되어 특정 작업이 완료 된 후에 호출되는 함수이다. 결과적으로 "너는 나중에 호출할게" 라고 의미할 수 있다.
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
function sayGoodbye() {
console.log("Goodbye!");
}
greet("황성진", sayGoodbye);
// 출력
// Hello, 황성진!
// Goodbye!
비동기 처리 예제
콜백 함수는 비동지 작업에서 특히 유용하다
function fetchData(callback) {
setTimeout(function() {
const data = { id: 1, message: "Hello, world!" };
callback(data);
}, 2000);
}
function displayData(data) {
console.log("데이터 받기 완료 :", data);
}
fetchData(displayData);
// 2초 후 출력: 데이터 받기 완료 : { id: 1, message: 'Hello, world!' }
장점
- 간단하고 이해하기가 쉽다
- 모든 브라우저에서 지원된다
- 특정 이벤트가 발생했을때 원하는 기능을 실행항 수 있다
단점
- 무한 콜백 지옥의 발생 가능성
- 에러처리가 복잡하다
- 가독성이 떨어진다
Callback Hell(콜백 지옥)
콜백 함수가 중첩되면서 코드의 가독성이 크게 떨어지는 현상을 말한다.
아래 이미지가 가장 잘 표한한 예시라고 생각된다.
Promise(프로미스)
Promise는 비동기 작업의 최종 완료 혹은 실패와 그 결과값을 나타내는 객체이다.
Promise가 생설될때 꼭 결과를 알 수 없는 작업의 최종 결과값을 나타낸다.
Promise는 세가지 상태중 하나를 가진다
- 대기(Pending) : 초기 상태, 이행되거나 거부되지 않은 상태
- 이행(Fulfilled) : 연산이 성공적으로 완료됨
- 거부(Rejected) : 연산이 실패함
let myPromise = new Promise(function(resolve, reject) {
// 비동기 작업 수행
setTimeout(() => {
const success = true;
if (success) {
resolve("성공했습니다!"); // 성공 시 resolve 호출
} else {
reject("실패했습니다."); // 실패 시 reject 호출
}
}, 2000);
});
// 프로미스 소비
myPromise
.then(result => console.log(result)) // 성공 시 실행
.catch(error => console.log(error)); // 실패 시 실행
Promise Chaining(프로미스 체이닝)
Promise의 큰 장점 중 하나는 여러 비동기 작업을 연속적으로 처리할 수 있는 체이닝 기능이다.
function stepOne(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(value);
resolve();
}, 3000);
});
}
function stepTwo(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(value);
resolve();
}, 2000);
});
}
function stepThree(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000);
});
}
// 프로미스 체이닝으로 순차적 실행
stepOne(1)
.then(() => stepTwo(2))
.then(() => stepThree(3))
.then(() => {
console.log("모든 단계 완료");
});
장점
- 콜백 지옥을 해결하여 코드의 가독성을 향상 시킨다
- 체이닝을 통해 연속적인 비동기 처리가 용이함
- catch 메서드를 통해서 오류에 대한 처리가 쉬움
- 여러 비동기 작업을 병렬로 처리 가능 (Promise.all, Promise.race 등)
단점
- ES6 이전 브라우저에서는 polyfill 이 필요
- 코드가 여전히 복잡할 수 있음
- 조건부 로직 처리가 복잡함
Async/Await
가장 친숙한 개념이다.
Async/Await은 ES2017(ES8)에서 도입된 문법으로, Promise를 더욱 쉽게 사용할 수 있게 해주는 특별한 문법이다.
Async/Awit을 사용하면 비동기 코드를 마치 동기 코드처럼 작성 할 수 있다.
- async 함수는 항상 Promise를 반환한다
- await 은 Promise 가 처리될때 까지 함수 실행을 일시 중단한다
async function fetchUserData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const userData = await response.json();
console.log(userData);
return userData;
} catch (error) {
console.error('유저정보 받아오기 오류', error);
throw error;
}
}
// 함수 호출
fetchUserData()
.then(data => console.log('유저정보:', data))
.catch(error => console.error('에러발생:', error));
Prmoise 와 Async/Await 의 비교
// Promise
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
.catch(error => console.log(error));
// Async/Await
async function fetchTodo() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const json = await response.json();
console.log(json);
} catch (error) {
console.log(error);
}
}
fetchTodo();
장점
- 코드의 가독성 향상
- 직관적인 오류 처리(try-catch 구문 사용)
- 디버깅이 용이함
- 조건부 로직 처리가 쉬움
단점
- ES2017 이전의 브라우저에서는 트랜스파일러 필요
- 병렬 처리 시 추가 작업 필요
- await은 반드시 async 함수 구문내에서만 사용 가능
결론
자바스크립트에서 비동기 처리는 Callback, Promise, Async/Await 세 가지 방식으로 구현할 수 있다.
각 방식은 고유한 장단점을 가지고 있으며 상황에 따라 적절한 방법을 선택하는 것이 좋다.
Callback은 가장 기본적인 방식이지만 코드가 복잡해질 수록 가독성이 떨어지는 콜백 지옥 문제가 발생할 수 있다.
Promise는 콜백 지옥 문제를 해결하고 체이닝을 통해 연속적인 비동기 작업이 가능하지만 여전히 코드가 복잡해 질 수 있다.
Async/Await은 Promise 기반으로 동작하면서 동기 코드와 유사한 구조로 작성할 수 있어 가독성이 좋고 오류 처리가 직관적이다.
이렇게 현재 자바스크립트 개발에서는 Async/Await, Promise 가 주로 사용되며, 특히 Async/Await은 가독성과 오류 처리의 용이성으로 인해 많이선호된다(나도 제일 편해서 좋아한다) 하지만 모든 상황에 맞는 최고의 방식 은 존재할 수 없듯이 여러 방법론에 따라 다방면에서 고민하면서 항상 차선책을 찾으려 노력해야 된다고 생각한다.