✅비동기를 처리하는 3가지 방식인 🔑콜백 함수와 🔑Promise 그리고 🔑Async/Await를 직접 코드로 쳐보며 어떠한 차이가 있고 어떻게 사용할지 자알 알아두자!
자바스크립트 엔진
은 단 하나의 실행 컨텍스트 스택
을 갖게 되고 한 번에 하나의 태스크만 실행할 수 있는 싱글 스레드 방식
으로 동작한다.
비동기 처리를 구현하는 방식에 대해 알기 전에 위 문장에 대한 이해가 있어야한다.
따라서싱글 스레드 방식
은 결국 한 번에 하나의 태스크만 실행할 수 있어서 처리에 시간이 걸리는 태스크를 실행하는 경우 블로킹(작업 중단)이 발생한다.
<script> function sleep(func, delay) { const delayUntill = Date.now() + delay while(Date.now() < delayUntill); func() } function foo(){ console.log('foo') } function bar(){ console.log('bar') } sleep(foo, 3 *1000) // sleep 함수 3초 후에 foo 함수 호출 bar() // bar함수는 sleep 함수 실행 종료 후에 호출되므로 3초 이상 호출 되지 못하고 블로킹 된다. </script>
- 위 코드처럼 현재 실행 중인 태스크가 종료할 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고한다.
-WHAT IS❓
- 자바스크립트는
비동기 처리
를 위한 하나의 패턴으로콜백함수
를 사용한다.
비동기 함수
는비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수 도 없기에비동기 함수
의 처리 결과(서버의 응답)에 대한 후속 처리는 비동기 함수 내부에서 수행해야 한다.- 이때
비동기 함수
에 비동기 처리 결과에 대한 후속 처리를 수행하는 콜백 함수를 전달하는 것이 일반적이다.
콜백 헬
🔥로 인해 1> 가독성이 나쁘고 2> 비동기 처리 중 발생한 에러의 처리가 곤란하여 여러 비동기 처리를 한 번에 처리하는데 있어 한계를 가진다.
- 콜백 헬이라는 단어에 대해서 많이 들어봤을 거다.
콜백 헬
이란 콜백 함수를 통해 비동기 처리 결과에 대한 후속 처리를 수행하는 비동기 함수가 비동기 처리를 가지고 또 다시 비동기 함수를 호출하면 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상을 말한다.
- 이건 뭐 장풍도 아니고... 콜백 함수로 비동기 처리 결과에 대한 후속 처리를 수행하기 위해 콜백 함수를 전달하면 저렇게 깊어지는 현상이...
<script>
try {
setTimeout(() => {
throw new Error('err!');}, 1000);
} catch(e) {
// 에러를 캐치하지 못한다.
console.error('catch err:', e)
}
</script>
비동기 처리
를 위한 콜백 패턴의 가장 심각한 문제는 에러 처리가 곤란하다는 것이다.
위 코드에서 1> setTimeout가 호출되면 콜스택에 푸시되어 실행되는데 setTimeout은 비동기 함수
이므로 콜백 함수가 호출 되는 것을 기다리지 않고 즉시 종료되어 콜스택에서 제거된다.
2> 이후 타이머가 만료되면 setTimeout 함수의 콜백 함수는 태스크 큐로 푸시되고 콜스택이 비어졌을 때 이벤트 루프에 의해 콜 스택으로 푸시되어 실행된다.
3> 중요한건 에러는 호출자 방향을 전파되어 즉, 콜 스택의 아래방향으로 전파된다는 것이다. 하지만 위 코드에선 setTimeout 함수의 콜백 함수를 호출 한 것은 setTimeout 함수가 아니므로 setTimeout 함수의 콜백 함수가 발생시킨 에러는 catch 블록에서 캐치되지 않는다.
-WHAT IS❓
- ES6에서
비동기 처리
를 위한 또 다른 패턴으로서Promise
를 도입했다.
Promise
는 전통적인 콜백 패턴이 가진 단점을 보완하며비동기 처리
시점을 명확하게 표현할 수 있다는 장점을 가진다.
Promise 생성자 함수
를new 연산자
와 함께 호출하면프로미스(객체)
를 생성한다.- Promise 생성자 함수는
비동기 처리
를 수행할 콜백 함수를 인수로 전달받는데 이 콜백 함수는resolve
와reject
함수를 인수로 전달받는다.
<script>
componentDidMount() {
const promiseGet = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
// onload 이벤트 핸들러가 비동기로 동작한다.
if (xhr.status === 200) {
// 서버의 응답을 전달 받으면 resolve 함수를 호출
resolve(JSON.parse(xhr.response));
} else {
reject(new Error`${xhr.status}`());
}
};
});
};
const response = promiseGet("https://jsonplaceholder.typicode.com/users/1");
console.log(response);
}
</script>
비동기 처리
를 수행한다. 이때 비동기 처리를 성공하면 콜백 함수의 인수로 전달받은 resolve 함수
를 호출하고 비동기 처리가 실패하면 reject 함수
를 호출한다.Promise
는 현재 비동기 처리
가 어떻게 진행되고 있는지를 나타내는 상태 정보
를 갖는다.
- 생성된 직후의 프로미스는 기본적으로
pending 상태
이고 이후 비동기 처리가 수행되면 비동기 처리 결과에 따라서 프로미스의 상태가 변경된다.- 프로미스의 상태는
resolve
또는reject
함수를 호출하는 것으로 결정된다.- 비동기 처리
성공 시
:resolve 함수 호출
=> 프로미스를fullfilled
상태로 변경- 비동기 처리
실패 시
:reject 함수 호출
=> 프로미스를rejected
상태로 변경
비동기 처리 상태
와 더불어 비동기 처리 결과
도 상태로 갖게 된다.
- fullfilled 또는 rejectet 상태를 settled 상태로 pending이 아닌 상태로서 비동기 처리가 수행된 상태를 의미하고 settled가 되면 다른 상태로 변화할 수 없다.
- 따라서 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체라고 할 수 있다.
- 프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속 처리를 해야한다.
이를 위해 후속 메서드then
,catch
,finally
를 제공한다.- 프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백함수가 선택적으로 호출되고 이때 후속 처리 메서드의 콜백 함수에 프로미스의 처리 결과가 인수로 전달된다.
- 모든 후속 처리 메서드는 프로미스를 반환하며 비동기로 동작한다.
1> then
*
- then 메서드는 두개의 콜백 함수를 인수로 전달받는다.
첫 번째 콜백함수는 비동기 처리 성공 시 호출되는 성공 처리 콜백 함수, 두 번재 콜백 함수는 비동기 처리 실패 시 호출되는 실패 처리 콜백 함수이다.<script> // fullfilled new Promise(resolve=>resolve('fullfilled')) .then(v => console.log(v),e => console.log(error(e))) // rejected new Promise((_, reject)=>reject(new Error('rejected'))) .then(v => console.log(v), e => console.error((e))) </script>
- 주의 할 점은 then 메서드는 언제나 프로미스를 반환한다는 것으로 만약 then 메서드의 콜백 함수가 프로미가 아닌 값을 반환하면 그 값을 암묵적으로 resolve 또는 reject하여 프로미스를 생성해 반환한다.
2> catch
- catch 메서드는 한 개의 콜백 함수를 인수로 전달 받고 catch 메서드의 콜백 함수는 프로미스가 rejected 상태인 경우에만 호출된다.
<script> new Promise((_, reject)=>reject(new Error('rejected'))) .catch(e => console.log(e) </script>
- catch 메서드는 then과 동일하게 동작한다. 따라서 언제나 프로미스를 반환한다.
3> finally
- finally 메서드는 한 개의 콜백 함수를 인수로 전달 받는다. finally 콜백 함수는 성공과 실패 상관없이 무조건 한 번 호출된다.
- finally메서드는 프로미스의 사태와 상관없이 공통적으로 수행해야할 처리 내용이 있을 시 유용하다.
<script> new Promise(()=>{}) .finally(() => console.log('finally')) </script>
- 프로미스로 구현한 비동기 함수 get을 사용해 후속처리를 구현한 코드
🔅참고🔅)
💡TIP)
- 비동기 처리 함수를 왜! 언제! 어떻게! 쓰면 좋은지를 잘 알아두자
- 딥다이브 자바스크립트 - 비동기 part