자바스크립트에서 비동기 처리는 주로 콜백 함수, 프로미스, 그리고 async/await를 사용하여 수행된다. 각각의 특성과 사용 방법, 장단점을 자세히 살펴보자. 😉
콜백(Callback)
콜백 함수는 다른 함수에 인자로 전달되는 함수로, 어떤 이벤트가 발생한 후 또는 특정 작업이 완료된 후에 호출된다.
- 작동 방식
- A 함수에 B 함수를 인자로 넘겨주고, A 함수의 처리가 끝나면 B 함수를 호출한다.
- 비동기 작업(예: 파일 읽기, 요청 보내기 등)에 대한 콜백을 등록하고, 작업 완료 후 콜백이 실행된다.
- 장단점
- 장점
- 간단하고 직관적인 비동기 처리 방식
- 모든 자바스크립트 환경에서 널리 사용된다.
- 단점
- 중첩된 콜백 함수(콜백 지옥)로 인해 코드의 가독성과 유지보수성이 떨어진다.
- 에러 처리가 복잡하고, 콜백 체인의 어느 부분에서 에러가 발생했는지 추적하기 어렵다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| function fetchData(callback) {
setTimeout(() => {
callback(null, "Data fetched using callback");
}, 2000);
}
fetchData((error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data); // 'Data fetched using callback'
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| // 첫 번째 데이터를 가져오는 비동기 함수
function fetchData1(callback) {
setTimeout(() => {
console.log("Data 1 fetched");
callback(null, "Data1");
}, 1000);
}
// 두 번째 데이터를 가져오는 비동기 함수
function fetchData2(data1, callback) {
setTimeout(() => {
console.log("Data 2 fetched");
callback(null, "Data2");
}, 1000);
}
// 세 번째 데이터를 가져오는 비동기 함수
function fetchData3(data2, callback) {
setTimeout(() => {
console.log("Data 3 fetched");
callback(null, "Data3");
}, 1000);
}
// 콜백 지옥 예시
fetchData1((error1, data1) => {
if (error1) {
console.error(error1);
return;
}
fetchData2(data1, (error2, data2) => {
if (error2) {
console.error(error2);
return;
}
fetchData3(data2, (error3, data3) => {
if (error3) {
console.error(error3);
return;
}
console.log("All data fetched:", data1, data2, data3); // Data1, Data2, Data3
});
});
});
|
프로미스 (Promises)
프로미스는 비동기 작업의 결과를 나타내는 객체이다. 프로미스는 세 가지 상태를 가진다.
대기(pending), 이행(fulfilled), 거부(rejected)
- 작동 방식
- new Promise를 통해 프로미스 객체를 생성하고, 비동기 작업을 수행한다.
- 비동기 작업이 성공적으로 완료되면 resolve 함수를 호출하고, 실패하면 reject 함수를 호출한다.
- .then() 메서드를 사용하여 이행 상태의 결과를 처리하고, .catch() 메서드를 사용하여 거부 상태의 결과를 처리한다.
- 장단점
- 장점
- 콜백 지옥 문제를 해결하고, 체이닝을 통해 가독성 좋은 코드를 작성할 수 있다.
- 중앙집중식 에러 처리가 가능하다.
- 단점
- 프로미스가 한 번 거부되면 다시 재시도할 수 없다.
- 프로미스를 과도하게 사용하면 코드가 복잡해질 수 있다.
1
2
3
4
5
6
7
8
9
10
11
| function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched using promise");
}, 2000);
});
}
fetchData()
.then((data) => console.log(data)) // 'Data fetched using promise'
.catch((error) => console.error(error));
|
Promise.all
Promise.all은 여러 프로미스를 병렬로 처리할 때 사용한다. 모든 프로미스가 이행되면, 각 프로미스의 결과를 모아 배열로 반환한다. 하나라도 거부되면, Promise.all은 즉시 거부된다.
1
2
3
4
5
6
7
8
9
10
11
| let promise1 = Promise.resolve("Promise 1 resolved");
let promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "Promise 2 resolved");
});
let promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "Promise 3 resolved");
});
Promise.all([promise1, promise2, promise3])
.then((values) => console.log(values)) // ['Promise 1 resolved', 'Promise 2 resolved', 'Promise 3 resolved']
.catch((error) => console.error(error));
|
async/await
async/await는 비동기 코드를 동기 코드처럼 보이게 하고 작성할 수 있게 하는 ES2017(ES8)에서 소개된 문법이다.
- 작동 방식
- async 함수는 항상 프로미스를 반환한다.
- 함수 내부에서 await 키워드를 사용하여 프로미스의 이행(fulfilled) 결과를 기다린다.
- await 키워드는 async 함수 내에서만 유효하다.
- 장단점
- 장점
- 동기 코드와 유사한 형태로 비동기 코드를 작성할 수 있어 가독성이 매우 높다.
- try/catch 블록을 사용한 일관된 에러 처리가 가능하다.
- 단점
- await 키워드를 남용하면 순차적인 코드 실행으로 인해 성능 문제가 발생할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| async function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched using async/await");
}, 2000);
});
}
async function showData() {
try {
const data = await fetchData();
console.log(data); // 'Data fetched using async/await'
} catch (error) {
console.error(error);
}
}
showData();
|