
콜백 함수(Callback Function)란 다른 함수의 인자로 전달되어 실행되는 함수를 말합니다. 자바스크립트에서는 일급 객체(First-class Object)로서 함수를 값처럼 전달할 수 있기 때문에, 콜백 패턴은 아주 일반적으로 사용됩니다.
function greet(name, callback) {
console.log("안녕하세요, " + name);
callback();
}
function sayBye() {
console.log("안녕히 가세요!");
}
greet("동언", sayBye);
위 코드에서 sayBye 함수는 greet 함수의 콜백으로 전달되어 나중에 실행됩니다.
콜백 함수는 간단한 비동기 흐름에서는 유용하지만, 여러 비동기 작업을 중첩해서 순차적으로 처리해야 할 경우 다음과 같은 문제들이 발생합니다.
readFile("user.json", (err, data) => {
if (err) throw err;
parseData(data, (err, result) => {
if (err) throw err;
fetchUser(result.id, (err, user) => {
if (err) throw err;
console.log(user);
});
});
});
💡 이러한 단점을 해결하기 위해 등장한 것이 바로 Promise입니다.
Promise는 자바스크립트의 비동기 처리를 위한 객체입니다. 비동기 작업의 최종 성공 또는 실패를 나타내는 값을 미래 시점에 전달하겠다는 약속을 의미합니다.
pending: 대기 중 (아직 결과가 없음)fulfilled: 작업 성공rejected: 작업 실패const promise = new Promise((resolve, reject) => {
// 비동기 로직
});
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("🎉 데이터 로드 성공");
} else {
reject(new Error("❌ 데이터 로드 실패"));
}
}, 2000);
});
}
fetch(), axiossetTimeout/setInterval 대체| 상태 | 설명 |
|---|---|
pending | 초기 상태, 아직 결과가 없음 |
fulfilled | 비동기 작업이 성공적으로 완료됨 |
rejected | 비동기 작업이 실패하여 오류 발생 |
resolve(value) → fulfilled 상태로 전이reject(error) → rejected 상태로 전이const promise = new Promise((resolve, reject) => {
resolve("성공");
reject("실패"); // 무시됨
});
const promise = new Promise((resolve, reject) => {
// 비동기 작업
setTimeout(() => {
const ok = true;
if (ok) resolve("작업 완료");
else reject(new Error("에러 발생"));
}, 1000);
});
resolve(value) → 정상 결과 전달 reject(error) → 에러 객체 전달Promise의 가장 큰 장점 중 하나는 체이닝(Chaining) 을 통해 여러 비동기 작업을 순차적으로 연결할 수 있다는 점입니다.
then, catch
then()메서드는 성공 결과를, catch()메서드는 실패 결과를 처리합니다. 이 메서드들은 새로운 Promise를 반환하므로 계속해서 연결할 수 있습니다.
fetchData()
.then((data) => {
console.log("성공", data);
})
.catch((err) => {
console.error("실패", err.message);
});
finally()메서드는 Promise의 성공/실패 여부와 상관없이 무조건 한 번 실행됩니다. 주로 로딩 스피너 제거 등 마무리 작업에 사용됩니다.
fetchData()
.finally(() => {
console.log("작업 종료");
});
Promise.all([fetchA(), fetchB(), fetchC()])
.then(([a, b, c]) => console.log(a, b, c))
.catch(console.error);
Promise.race([fetchSlow(), fetchFast()])
.then(console.log)
.catch(console.error);
Promise.allSettled([fetch1(), fetch2()])
.then((results) =>
results.forEach((res) =>
res.status === "fulfilled"
? console.log("성공:", res.value)
: console.error("실패:", res.reason)
)
);
Promise.any([fail1(), fail2(), success()])
.then(console.log)
.catch((e) => console.error("모두 실패", e));
async/await는 Promise를 동기 코드처럼 사용할 수 있게 해주는 문법입니다.
async 함수는 항상 Promise를 반환합니다.await은 해당 Promise의 결과가 나올 때까지 기다립니다.async function loadUserProfile() {
try {
const name = await fetchName(); // Promise<string>
const age = await fetchAge(); // Promise<number>
const job = await fetchJob(); // Promise<string>
return { name, age, job };
} catch (err) {
console.error("에러:", err);
throw err;
}
}
| 장점 | 설명 |
|---|---|
| 가독성 ↑ | 중첩 없는 직관적 코드 작성 가능 |
| 에러 처리 용이 | try/catch 문법으로 일괄 처리 가능 |
| 디버깅 쉬움 | 호출 스택 간결 |
await는 반드시 async 함수 안에서 사용Promise.all() 사용이 더 효율적기존 콜백 패턴을 Promise로 감싸는 예제:
function legacy(callback: (value: string) => void) {
setTimeout(() => {
callback("✅ 완료");
}, 1000);
}
function wrapped(): Promise<string> {
return new Promise((resolve) => {
legacy((result) => {
resolve(result);
});
});
}
🧠
util.promisify()(Node.js 내장 유틸)도 유사한 기능을 제공합니다.
async function getWeatherInfo() {
try {
const [temp, wind] = await Promise.all([
fetchTemperature(),
fetchWindSpeed(),
]);
return { temp, wind };
} catch (err) {
console.error("날씨 정보를 불러오는데 실패했습니다.");
throw err;
}
}
정리