프로미스, 콜백 함수

미음제

·

2022. 11. 8. 22:32

콜백 함수

 

비동기 처리를 위한 하나의 패턴으로 사용된다. 콜백 헬로 가독성이 저해되고 에러 핸들링이 곤란하고 여러 개의 비동기를 처리하는데 한계가 있다.

 

콜백 헬

 

함수를 익명 함수로 전달하는 과정이 반복되어 indent가 깊어지는 현상을 의미한다. 서버 통신과 같은 비동기 작업을 수행하기 위한 코드에서 자주 발생한다.

 

프로미스

 

ES6에 비동기 처리를 위해 도입된 패턴으로 비동기 처리 상태와 처리 결과를 관리하는 객체이다.

 

프로미스 등장 배경

 

비동기 처리를 위한 패턴

 

  • ES6 : Promise, Generator
  • ES7 : async/await

 

비동기 함수는 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수 없다. 비동기 처리 결과나 후속 처리 과정은 비동기 함수 내부에서 처리해야 한다.

 

비동기 함수 내부에서 처리하기 위해 일반적으로 콜백 함수(성공, 실패)를 전달한다.

 

이 과정에서 콜백 함수가 다른 콜백 함수를 호출해야 하는 경우 콜백 헬이 발생하게 된다.

 

에러 처리의 한계

 

try {
	setTimeout(() => throw new Error('Error Ocuur!'); }, 1000);
} catch(e) {
	console.error('캐치한 에러', e); // 에러를 캐치하지 못한다.
}

 

정상 작동할 것처럼 보이지만, 에러를 캐치하지 못한다. setTimeout() 함수는 비동기 함수로 콜백 함수가 호출되는 것을 기다리지 않고 즉시 종료되어 콜 스택에서 제거된다. 이후 타이머가 만료될 때 setTimeout() 함수의 콜백 함수 가 태스크 큐로 푸시되고 콜 스택이 비었을 때 이벤트 루프에 의해 콜 스택으로 푸시되어 실행된다.

 

콜백 함수 가 실행될 땐 이미 setTimeout() 함수가 콜 스택에서 제거된 상태이므로 하위 실행 컨텍스트가 setTimeout()이 아니다. 따라서 콜백 함수를 호출한 주체가 setTimeout() 함수가 아니므로 에러를 캐치하지 못한다.

 

에러 캐치는 호출자 방향으로 전파된다.

콜 스택의 아랫 방향(실행 중인 실행 컨텍스트가 푸시되기 직전에 푸시된 실행 컨텍스트 방향)으로 전파된다.

 


 

다시, 프로미스로 돌아와 프로미스 객체는 new 키워드와 생성자를 사용해 만든다.

생성자 함수는 비동기 처리를 수행하기 위해 실행 함수를 매개 변수로 받는다.

 

  • resolve() : 비동기 작업이 왼료되면 결과를 반환하는 용도
  • rejcect() : 비동기 작업이 실패한 경우 결과를 반환하는 용도

 

resolve, reject 함수를 호출하는 구문이 있는 경우 해당 내용이 실행되기 전까지(비동기 보장) then, catch 구문으로 넘어가지 않는다.

 

프로미스 상태 정보

 

resolve, reject 함수를 통해 프로미스 상태를 결정할 수 있고, 한번 변경된 경우(setteld → fullfilled or rejected) 다른 상태로 변화할 수 없다.

 

then

 

then 메서드는 두 개의 콜백 함수를 인자로 받을 수 있다.

 

  • 첫 번째 콜백 함수 : resolve()가 실행된 결과
  • 두 번째 콜백 함수 : reject()가 실행된 결과

 

catch 이전에 then 메서드가 reject() 결과를 받으면 앞선 then에서 에러를 캐치하므로 catch 이후의 후속 동작이 실행된다.

 

catch

 

then과 달리 한 개의 콜백 함수를 인자로 받을 수 있다. reject() 결과를 받아 처리한다.

 

프로미스 정적 메서드

 

Promise.resolve/reject

 

이미 존재하는 값을 래핑해 프로미스를 생성하기 위해 사용한다. 인수로 전달 받음 값을 resolve 혹은 reject 하는 프로미스를 생성한다.

 

const reolvePromise = Promise.resolve([1,2,3]);
const rejectPromise = Promise.reject(new Error('Error'));
// 각각 출력 결과 : [1, 2, 3,], Error: Error

 

Promise.all

 

여러 개의 비동기 처리를 모두 병렬 처리할 때 사용한다.

 

const requestData1 = () => {
	new Promise(resolve => setTimeout(() => resolve(1), 3000));
}
const requestData1 = () => {
	new Promise(resolve => setTimeout(() => resolve(2), 2000));
}
const requestData2 = () => {
	new Promise(resolve => setTimeout(() => resolve(3), 1000));
}
Promise.all([requestData1(), requestData2(), requestData3()])
	.then(console.log)
	.catch(console.error);
// [1, 2, 3], 3초 가량 소요

 

프로미스 요소를 갖는 이터러블을 전달받고, 모두 fullfiled 상태가 되면 모든 처리 결과를 배열에 저장해 새로운 프로미스를 반환한다.

 

모두 fullfilled 상태가 되면 종료되기 때문에 Promise.all 메서드가 종료하는 데 걸리는 시간은 가장 늦게 fullfilled 상태가 되는 프로미스의 처리 시간보다 조금 더 길다.

 

단 하나라도 rejected 상태가 되면 나머지 프로미스는 fullfilled 상태를 기다리지 않고 바로 종료된다.

 

Promise.race

 

Promise.all과 동일하게 프로미스 요로를 갖는 이터러블을 인수로 전달받는다. Promise.all과 달리 fullfilled 상태가 되는 것을 기다리는 것이 아니라 가장 먼저 fullfilld 상태가 된 프로미스 처리 결과를 resolve 하는 새로운 프로미스를 반환한다.

 

Promise.race([
	new Promise(resolve => setTimeout(() => resolve(1), 3000)),
	new Promise(resolve => setTimeout(() => resolve(2), 2000)),
	new Promise(resolve => setTimeout(() => resolve(3), 1000)),
])
	.then(console.log)  // 3
	.catch(cosole.error);

 

  • Promise.all : 순서 보장
  • Promise.race : 가장 빨리 resolve 된 순서

 

마이크로태스크 큐

 

setTimeout(() => console.log(1), 0);

Promise.resolve()
	.then(() => console.log(2))
	.then(() => console.log(3));

1 → 2→ 3처럼 보이지만, 2 → 3 → 1의 순서로 출력된다.

 

프로미스의 후속 처리 메서드(resolve, reject)의 콜백 함수는 태스크 큐가 아니라 마이크로태크스 큐에 저장되기 때문이다.

태스크 큐와 별도의 큐로, 프로미스의 후속 처리 메서드의 콜백 함수가 일시 저장되는 곳이다. 그 외의 비동기 함수의 콜백 함수나 이벤트 핸들러는 태스크 큐에 일시 저장된다.

 

마이크로태스크 큐는 태스크 큐 보다 우선순위가 높다. 이벤트 루프는 콜 스택이 비면 태스크 큐 보다 마이크로태스크 큐에 대기하고 있는 함수를 가져와 실행한다.

 

(이 부분은 일전에 TIL에 작성했던 부분에서 다시 공부해 볼 내용으로 메모해 두었던 내용이다. 추후에 더 공부해서 추가로 정리할 계획이다.)

 

 


 

이 내용은 '모던 자바스크립트 Deep Dive' 책을 보고 공부한 내용을 다시 정리한 것입니다.

 

참고

 

 

GitHub - prgrms-web-devcourse/FE-CoreJS-study-2022-: 📗 코어 자바스크립트 책 스터디

📗 코어 자바스크립트 책 스터디. Contribute to prgrms-web-devcourse/FE-CoreJS-study-2022- development by creating an account on GitHub.

github.com

모던 자바스크립트 Deep Dive - 이웅모 저자

반응형