ES8부터 도입된async, await은 보다 가독성 좋게 비동기 처리를 동기 처리처럼 동작하도록 구현할 수 있는 문법이다.
프로미스를 기반으로 동작하는데, then, catch, finally 의 후속 처리 메서드 없이도 프로미스가 동기처리처럼 처리결과를 반환하도록 구현할 수 있다.
async 키워드를 사용한 함수는 언제나 프로미스를 반환한다.
명시적으로 프로미스를 반환하지 않더라도 async 함수는 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.
await 키워드는 프로미스가 settled 상태가 될 때까지 대기한다.
settled : 비동기 처리가 수행된 상태
그리고 settled 상태가되면 프로미스가 resolve한 처리결과를 반환한다.
예를 들어보자.
async function fetchData() {
const res = await fetch('url')
await 키워드는 fetch 함수가 요청한 응답이 도착해서, fetch 함수가 반환한 promise가 settled 상태가 될 때까지 대기한다.
이후 settled 상태가 되면 프로미스가 resolve한 처리결과가 res 변수에 할당된다.
즉, await 키워드는 실행문을 잠깐 중지시켰다가 프로미스가 settled 상태가 되었을 때 다시 실행시킨다.
각각의 비동기 처리가 개별적으로 수행되어 순차적인 처리가 필요가 없다면, promise.all로 묶어서 await 키워드를 붙여주면 된다.
async function foo() {
const res = await promise.all([
new Promise(...
new Promise(...
비동기 처리를 위한 콜백 패턴은 에러처리에 한계가 있었다.
에러는 호출자 방향으로 전파된다.
즉 콜 스택의 아래방향인 실행 컨텍스트가 푸시되기 직전에 푸시된 실행 컨텍스트 방향으로 전파된다. 하지만 비동기 함수의 콜백함수를 호출한 것은 비동기 함수가 아니기 때문에 try catch 문을 사용해 에러로 캐치할 수 없다.
예시를 보며 이해해보자.
try {
setTimeout(() => { throw new Error('Error!');}, 1000);
} catch (e) {
console.error('캐치한 에러', e);
}
이 때 비동기 함수이므로 콜백함수가 호출되는 것을 기다리지 않고 즉시 종료되어 콜 스택에서 제거된다.
그런데 콜백함수가 실행될 때 setTimeout 함수는 이미 콜스택에서 제거되었으므로, 콜백함수를 호출한 것이 setTimeout 함수가 아니게 된다.
왜 호출자가 아니라는 거지?
콜백함수의 실행컨텍스트가 실행중일 때 하위에 setTimeout함수의 실행컨텍스트가 있어야 호출자라고 할 수 있다.
에러는 호출자 방향으로 전파된다.
즉, 콜 스택에 아래방향 = 실행중이 함수 직전에 푸시된 실행컨텍스트 방향으로 전파된다.
그런데 콜백함수의 호출자는 setTimeout함수가 아니게 되므로, setTimeout함수의 콜백함수가 발생시킨 에러는 catch 블록에서 캐치되지 않는다.
결국 비동기 처리를 위한 콜백 패턴은 콜백 헬 뿐 아니라 에러처리가 곤란하다는 문제가 있다.
이것을 극복하기 위해 프로미스가 도입되었다.
async/await을 사용하면 promise의 후속처리메서드에 콜백함수를 전달해 비동기 처리결과를 후속처리할 필요가 없다.
마치 동기 처리처럼 프로미스를 사용할 수 있다.
다시 말해, 프로미스의 후속처리 메서드 없이 마치 동기 처리 처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있다.
콜백 함수를 인수로 전달받는 비동기 함수와는 달리,
프로미스를 반환하는 비동기 함수는 명시적으로 호출할 수 있기 때문에 호출자가 명확하다.