동기(Synchronous)와 비동기(Asynchronous)의 차이
동기 처리란 요청한 작업에 대해 완료 여부를 확인하고 다음 작업을 처리하는 방식이다.
즉, 현재 작업이 끝나기 전까지는 다른 작업을 시작하지 않으며, 작업 순서가 엄격하게 보장된다.
- 예시: 하나의 작업이 끝나야 그다음 작업이 실행됨
- 특징: 작업 순서 보장, 처리 방식 단순
- 단점: 시간이 오래 걸리는 작업이 있을 경우 전체 흐름이 지연될 수 있음
비동기 처리란 요청한 작업의 완료 여부를 기다리지 않고, 다음 작업을 즉시 실행하는 방식이다.
따라서, 작업 순서가 보장되지 않을 수 있으며, 완료된 시점에 맞춰 콜백 함수가 실행된다.
콜백 함수
비동기 작업이 완료되면 호출되는 함수의 의미로서 비동기 함수의 매개변수로 함수 객체를 넘기는 기법을 말한다.
함수 내부에서 함수 호출을 통해 비동기 작업의 결과를 받아서 인자로 주면 이를 통해 후속처리 작업이 가능하다.
- 예시: Ajax, Web API, setTimeout() 등
- 특징: 빠른 반응성, 효율적인 자원 활용
- 단점: 순서를 제어하기 어렵고, 콜백 중첩이 발생할 수 있음 (→ 콜백 헬)
콜백 헬(Callback Hell)
비동기 처리를 위해 여러 개의 콜백 함수를 중첩 사용하면, 코드의 가독성과 유지보수성이 급격히 떨어지는 현상을 말한다.
이를 해결하기 위해 Promise나 async/await 같은 문법이 도입되었다.
🤙🏻 Promise 란?
비동기 처리 상태와 처리 결과를 관리하는 객체이다.
콜백 지옥(Callback Hell)을 피하고, 비동기 흐름을 더 깔끔하게 제어할 수 있도록 도와준다.
📦 Promise 생성
const promise = new Promise((resolve, reject) => {
// Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행한다
if(/* 비동기 처리 성공을 나타내는 코드 */){
resolve('success result');
}
else{ // 비동기 처리 실패
reject('failure reason');
}
})
- promise 생성자는 비동기 처리를 수행할 resolve와 reject 함수를 콜백함수를 인자로 받는다.
- 이 콜백 함수 내부에서 비동기 작업을 수행한 뒤, 성공 시 resolve(), 실패 시 reject()를 호출한다.
⏳ Promise의 상태 (State)
pending = 비동기 처리가 아직 수행되지 않은 상태
상태 변경 조건 : 프로미스가 생성된 직후 기본 상태
생성 직후의 promise는 기본적으로 pending 된 상태이며 이후 비동기 처리 결과에 따라 프로미스의 상태가 변화한다.
fulfilled = 비동기 처리가 수행된 상태
상태 변경 조건 : 성공 시
비동기 처리 성공 → resolve() 호출
rejected = 비동기 처리가 수행된 상태
상태 변경 조건 : 실패 시
비동기 처리 실패 → reject() 호출
🔁 후속 처리 메서드
프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속처리를 해야 한다.
모든 후속 처리 메서드는 새로운 프로미스를 반환하며, 비동기로 동작한다
✅. then()
- fulfilled 상태가 되면 첫 번째 콜백 실행
- rejected 상태가 되면 두 번째 콜백 실행
promise.then(
(result) => console.log("성공:", result),
(error) => console.log("실패:", error)
);
❌. catch()
- rejected 상태일 때 호출됨
- . then()의 두 번째 인자와 동일한 역할을 하지만 가독성이 더 좋음
promise
.then((result) => console.log(result))
.catch((error) => console.error(error));
🔄 . finally()
- 성공 여부와 무관하게 무조건 한 번 실행
- 로딩 종료, 리소스 정리 등에 유용
promise
.then((result) => console.log(result))
.catch((error) => console.error(error))
.finally(() => console.log("작업 완료"));
🔗 프로미스 체이닝 (Promise Chaining)
myPromise
.then(handleFulfilledA)
.then(handleFulfilledB)
.then(handleFulfilledC)
.catch(handleError);
하지만. then()이 너무 많이 중첩되면 가독성이 떨어질 수 있다. 이를 해결하기 위해 async/await 문법이 등장했다.
⏱ async / await
async/await는 Promise 기반 코드를 동기식처럼 읽기 쉽게 만드는 문법이다.
🔸 async 함수
- 함수 앞에 async 키워드를 붙이면 항상 Promise를 반환한다.
- 반환값이 일반 값이라도 자동으로 Promise.resolve()로 감싼다.
async function foo() {
return 1; // 자동으로 Promise.resolve(1) 반환
}
🔸 await 키워드
- await은 Promise가 처리될 때까지 기다렸다가 그 결과를 반환한다.
- 반드시 async 함수 내부에서만 사용 가능하다.
async function getData() {
try {
const result = await someAsyncFunction();
console.log(result);
} catch (error) {
console.error(error);
} finally {
console.log("작업 종료");
}
}