콜백 함수들이 계속 중첩되면서 코드의 가독성이 크게 떨어지며, 콜백 함수들이 서로 얽혀 있어 에러가 발생했을 때 어느 부분에서 에러가 발생했는지 추적하기 어렵다. 이런 Callback Hell을 피하기 위해 Promise, Async / Await가 도입되었다.
02. Promise
Promise는 비동기 함수와 콜백 함수를 결합한 형태로, 비동기 작업이 완료되면 그 결과에 따라 다음 작업을 처리할 수 있도록 설계된 구조이다. Promise는 비동기 함수에서 실행 결과에 따라 성공 또는 실패를 처리하는 매커니즘을 제공하며, 이를 Producer-Consumer 패턴으로 이해할 수 있다.
function caller(callee) {
var produced = producing(); // 비동기 작업 수행
callee(produced); // 결과값을 소비하는 콜백 호출
}
caller(function callee(produced) {
consuming(produced); // 결과값 처리
});
비동기 함수(Executor) : caller = Producer : 성공적으로 마쳤을 때 resolve라는 콜백 함수를 통해 값을 전달
비동기 처리 후 수행할 함수(Executee) : callee = consumer : then, catch, finally를 이용해 값을 받아오며, new Promise에서 정상적으로 수행이 되면(.then) 값을 받아와 원하는 기능을 수행하는 콜백 함수 실행
#1. Promise와 Callback 함수 코드 차이
◼︎ Callback 함수
function getData(callbackFunc) {
$.get('url 주소/products/1', function(response) {
callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
});
}
getData(function(tableData) {
console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});
위 예시 코드는 ajax 통신 API를 이용해 지정된 url에서 1번 상품 데이터를 받아오는 코드이다.
비동기 처리를 위해 콜백 함수를 사용했으며 이 코드에 Promise를 적용하면 아래와 같다.
◼︎ Promise
function getData() {
// new Promise() 추가
return new Promise(function(resolve, reject) {
$.get('url 주소/products/1', function(response) {
if (response) {
// 데이터를 받으면 resolve() 호출
resolve(response);
} else {
// 오류가 발생하면 reject() 호출
reject(new Error("데이터를 가져오지 못했습니다."));
}
}).fail(function(error) {
// $.get() 요청 실패 시 reject() 호출
reject(error);
});
});
}
// getData()의 실행이 끝나면 호출되는 then(), catch(), finally()
getData()
.then(function(tableData) {
// resolve()의 결과 값이 여기로 전달됨
console.log("데이터를 성공적으로 가져왔습니다:", tableData);
})
.catch(function(error) {
// reject()의 결과 값이 여기로 전달됨
console.error("에러 발생:", error.message);
})
.finally(function() {
// 성공 여부와 관계없이 항상 실행
console.log("데이터 요청 작업이 완료되었습니다.");
});
콜백 함수 대신 Promise 구조로 코드를 변경하였다.
Promise를 사용하면 new Promise(), resolve(), then()과 같은 Promise API를 통해 비동기 작업을 보다 직관적이고 체계적으로 관리 가능하다. 이러한 Promise API는 아래에서 더 자세히 다룰 것이다.
then(): Promise가 성공적으로 완료되면 실행. 여기서 resolve()에 전달된 값이 then() 함수로 전달
catch(): Promise가 실패하거나 에러가 발생하면 실행. reject() 또는 네트워크 에러와 같은 문제에서 catch()가 호출
finally(): 성공 여부와 관계없이 Promise의 작업이 완료된 후 항상 실행.
#2. Promise 3가지 상태
프로미스를 사용할 때 알아야 하는 가장 기본적인 개념이 바로 프로미스의 상태이다. 여기서 말하는 상태란 프로미스의 처리 과정을 의미한다.new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다.
Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
◼︎ Pending(대기)
new Promise(function(resolve, reject) {
// ...
});
new Promise() 메서드를 호출하면 Pending(대기) 상태가 된다.
new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject가 있다.
◼︎ Fulfilled(이행)
function getData() {
return new Promise(function(resolve, reject) {
var data = 100;
resolve(data);
});
}
// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
console.log(resolvedData); // 100
});
콜백 함수의 인자 resolve를 위와 같이 실행하면 fulfilled(이행) 상태가 된다.
이행 상태가 되면 위와 같이then()을 이용하여 처리 결과 값을 받을 수 있다.
◼︎ Rejected(실패)
function getData() {
return new Promise(function(resolve, reject) {
reject(new Error("Request is failed"));
});
}
// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
console.log(err); // Error: Request is failed
});
new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로resolve와reject를 사용할 수 있는데, 여기서reject를 위와 같이 호출하면 Rejected(실패) 상태가 됩니다. 그리고, 실패 상태가 되면 실패 처리의 결과 값을catch()로 받을 수 있다.
#3. Promise Hell = Nested Promise
Promise Hell은 여러 개의 비동기 작업이 서로 의존적이고 순차적으로 실행되어야 할 때, 코드가 중첩되면서 복잡해지고 가독성이 떨어지는 문제를 의미한다. Promise 의 결과가 그 다음 Promise 실행에 필요한 경우에 발생하며, 이는 콜백 함수가 중첩될 때 발생하는 Callback Hell과 비슷하게 Promise 사용 시에도 중첩 구조가 반복되면 발생한다.
[JavaScript] 비동기 처리 : 콜백 함수와 Promise
01. Callback
콜백 함수는 함수의 실행 권한을 다른 함수에 넘기는 것을 의미한다.
비동기 함수와 콜백은 별개의 개념이지만, 비동기 작업이 완료된 후 실행되어야 하는 로직을 처리할 때 자주 사용된다.
예를 들어, 서버에 데이터를 요청하는 등의 상황에서 작업이 완료된 후 특정 동작을 해야 할 때 사용된다.
콜백 함수의 역할은 작업이 완료된 후 어떤 작업을 해야 할 지 정의하는 것이며, 이를 통해 자바스크립트는 효율적인 비동기 처리를 할 수 있다.
위 코드는 비동기 작업을 처리하는 콜백 함수의 예시를 보여준다. 해당 코드를 실행하면 다음과 같이 동작한다.
즉, 자바스크립트는 setTimeout 내부에서 콜백 함수를 대기 상태로 두고, 2초가 지난 후에야 콜백 함수를 실행하게 된다.
비동기 작업은 이러한 시간이 오래 걸리는 작업을 백그라운드에서 처리하고, 해당 작업이 끝났을 때 필요한 동작을 콜백 함수를 통해 수행하도록 한다. 하지만 서로 의존적인 비동기 작업들이 순차적으로 실행되어야 할 때 Callback Hell이 발생할 수 있다.
💡 Callback Hell
Callback Hell은 비동기 작업들이 중첩되어 실행될 때 발생하는 코드 구조의 복잡성을 의미한다.
자바스크립트에서는 비동기 작업을 처리할 때 콜백 함수를 자주 사용하지만, 비동기 작업들이 서로 의존적일 때, 즉 하나의 작업이 끝난 후에 다음 작업을 실행해야 하는 경우가 발생하면 콜백 함수가 중첩되기 시작한다.
이 중첩이 많아지면 코드를 이해하기 어렵고 가독성도 떨어지는 문제가 발생한다.
위 예시 코드는 Callback Hell을 볼 수 있는 예시이다.
콜백 함수들이 계속 중첩되면서 코드의 가독성이 크게 떨어지며, 콜백 함수들이 서로 얽혀 있어 에러가 발생했을 때 어느 부분에서 에러가 발생했는지 추적하기 어렵다. 이런 Callback Hell을 피하기 위해 Promise, Async / Await가 도입되었다.
02. Promise
Promise는 비동기 함수와 콜백 함수를 결합한 형태로, 비동기 작업이 완료되면 그 결과에 따라 다음 작업을 처리할 수 있도록 설계된 구조이다. Promise는 비동기 함수에서 실행 결과에 따라 성공 또는 실패를 처리하는 매커니즘을 제공하며, 이를 Producer-Consumer 패턴으로 이해할 수 있다.
#1. Promise와 Callback 함수 코드 차이
◼︎ Callback 함수
위 예시 코드는 ajax 통신 API를 이용해 지정된 url에서 1번 상품 데이터를 받아오는 코드이다.
비동기 처리를 위해 콜백 함수를 사용했으며 이 코드에 Promise를 적용하면 아래와 같다.
◼︎ Promise
콜백 함수 대신 Promise 구조로 코드를 변경하였다.
Promise를 사용하면 new Promise(), resolve(), then()과 같은 Promise API를 통해 비동기 작업을 보다 직관적이고 체계적으로 관리 가능하다. 이러한 Promise API는 아래에서 더 자세히 다룰 것이다.
#2. Promise 3가지 상태
프로미스를 사용할 때 알아야 하는 가장 기본적인 개념이 바로 프로미스의 상태이다. 여기서 말하는 상태란 프로미스의 처리 과정을 의미한다. new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다.
◼︎ Pending(대기)
new Promise() 메서드를 호출하면 Pending(대기) 상태가 된다.
new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject가 있다.
◼︎ Fulfilled(이행)
콜백 함수의 인자 resolve를 위와 같이 실행하면 fulfilled(이행) 상태가 된다.
이행 상태가 되면 위와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다.
◼︎ Rejected(실패)
new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있는데, 여기서 reject를 위와 같이 호출하면 Rejected(실패) 상태가 됩니다. 그리고, 실패 상태가 되면 실패 처리의 결과 값을 catch()로 받을 수 있다.
#3. Promise Hell = Nested Promise
Promise Hell은 여러 개의 비동기 작업이 서로 의존적이고 순차적으로 실행되어야 할 때, 코드가 중첩되면서 복잡해지고 가독성이 떨어지는 문제를 의미한다. Promise 의 결과가 그 다음 Promise 실행에 필요한 경우에 발생하며, 이는 콜백 함수가 중첩될 때 발생하는 Callback Hell과 비슷하게 Promise 사용 시에도 중첩 구조가 반복되면 발생한다.
위 코드에서 하나의 then() 블록 안에서 또다른 then()을 중첩하여 호출하게 된다.
비동기 작업들이 깊이 중첩되면서 코드의 가독성이 떨어지고, 중첩된 구조에서는 각 단계마다 에러 처리가 필요하지만 관리가 어려운 문제가 발생한다.
#4. Promise Chain
Promise Chain은 위와 같은 Promise Hell의 문제를 해결하기 위해 체인 방식을 사용해 각 작업을 순차적으로 실행하고 결과를 전달하는 구조이다. 체이닝을 통해 중첩을 피하고, 한 번에 하나의 작업만을 처리하여 코드의 가독성과 유지보수성을 개선한다.
중첩 없이 then()을 연속적으로 호출하여 작업을 처리하므로 코드의 가독성이 좋아진다.
또한 각 then()에서 이전 비동기 작업의 결과값을 받아 다음 작업을 수행하므로, 순차적인 실행 흐름을 명확하게 유지할 수 있으며, 모든 then() 블록을 통합해 catch()로 일관된 에러 처리를 할 수 있어 오류 발생 시 관리가 쉬워진다.
Promise Chain은 다양한 작업을 순차적으로 처리하면서도 중간에 새로운 작업을 쉽게 추가하거나 수정할 수 있다.
'ASAC > Front-End' 카테고리의 다른 글