
비동기 작업은 순서를 보장하기 않기 때문에, 비동기 작업이 순서가 있는 것처럼 보이도록 동기적 표현이 필요하다.
Promise는 콜백 지옥을 해결하기 위해 ES6(ES2015)에서 도입된 패턴으로, JS의 비동기 처리를 위한 객체이다.
비동기 작업의 최종 성공(resolve) 또는 실패(reject)를 나타내는 객체로, 비동기 작업을 쉽게 처리할 수 있게 해준다.
const promiseObj = new Promise(function(resolve, reject) {
const result = 비동기함수();
if (비동기작업결과) {
resolve(result);
} else {
reject(error);
}
}).then(function (prevResult) {
// Promise chaining
return new Promise(function (resolve) {
resolve(prevResult);
})
})
function promiseSetTimeout (ms: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms)
})
}
function main() {
const delay500ms = promiseSetTimeout(500);
delay500ms.then(() => {
console.log("500ms 딜레이 후 출력")
})
}
Promise의 비동기 처리 방식을 더 직관적으로 사용할 수 있게 도와주는 syntax sugar이다.
비동기 작업을 수행하고자 하는 함수 앞에 async를 붙여주고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 된다.
비동기 코드를 동기 코드처럼 작성할 수 있어 가독성이 Promise보다 좋아진다.
try…catch 블록을 통해 오류 처리도 용이하게 할 수 있다.
Promise를 사용하면서 .catch()를 통해 에러 처리를 하지 않거나, async/await에서 try…catch 블록을 생략하면 비동기 작업 중에 발생하는 오류(네트워크 에러 등)를 처리하지 못할 가능성이 있다. 이렇게 되면 코드가 중간에 실패해도 문제를 인지하지 못하거나 프로그램이 예기치 않게 종료되게 된다.
따라서 catch()나 try…catch 블록을 통해 에러 처리를 해주는 것이 좋다.
여러 비동기 작업을 순차적으로 실행할 때, Promise 체인에서 .then() 체인 중 하나라도 누락되면 오류가 발생한다.
fetch('url')
.then(() => fetch('url2'))
.then(() => console.log('All done'))
.catch(console.error);
await으로 값이 들어오도록 기다리고 있지만 Promise가 resolve되지 않으면 코드가 계속 대기 상태에 머물게 된다. 이 경우 타임아웃을 설정해주는 것이 좋다.
const fetchData = async () => {
const data = await new Promise(() => {}) // resolve/reject가 없으므로 대기
console.log(data); // 여기에 도달하지 않음
}
아래는 Promise.race()를 사용해 여러 개의 Promise 중 가장 먼저 완료된 Promise의 결과를 반환하는 방식으로 타임아웃을 설정해 무한 대기를 방지하는 코드이다.
const fetchWithTimeout = (url, timeout = 5000) {
// 무한 대기 상태 방지 Promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timed out')), timeout);
});
const fetchPromise = fetch(url);
return Promise.race([fetchPromise, timeoutPromise]);
}
async function fetchData() {
try {
const response = await fetchWithTimeout('https://api.com/data', 3000);
const data = await reponse.json();
console.log(data);
} catch (error) {
console.error(error); // 타임아웃 발생 시 에러 출력
}
}
fetchData();
제너레이터는 yield 키워드를 사용해 함수 실행을 중단하고 다시 시작할 수 있는 패턴으로, 비동기 작업의 흐름을 단계적으로 처리할 때 유용하게 사용된다. 비동기 흐름을 직접 제어할 수 있는 방법 중 하나이다.
function* generatorFunc () {...}
iterator
- iterator객체는 next라는 메서드를 가지고 있음
- next 메서드로 자기 자신에 있는 요소들을 순환할 수 있는 구조
- 비동기 작업이 완료되는 시점마다 then 사용하듯이 next 메서드를 호출하면 Generator 함수 내부 소스가 위에서 아래로 순차적으로 진행된다.
- 제너레이터 함수는 화살표 함수를 사용할 수 없다.
RxJS는 이벤트 스트림을 처리할 때 유용한 패턴이다. Observale을 통해 콜백 함수나 Promise보다 더 복잡한 비동기 작업을 관리할 수 있다. 예를 들어, 여러 이벤트의 연속 처리나 대량의 데이터를 비동기적으로 처리할 때 유용하다.
const { fromEvent } = rxjs;
const button = document.querySelector('button');
const clicks = fromEvent(button, 'click');
clicks.subscribe(() => console.log('Button clicked'));