Async, Await (feat. Generator)
Async, Await
async/await는 ES7(2017)부터 추가된 것으로, 이를 사용하면 비동기 코드를 마치 동기 코드처럼 보이게 작성할 수 있음
명세의 차이가 있을 뿐 async 함수를 호출하면 명시적으로 Promise 객체를 생성하여 리턴하지 않아도 Promise 객체를 리턴함
async function greet(){
return 'hi!'
}
const hi = greet()
console.log(hi) // Promise {<fulfilled>: 'hi!'}
▶ Async, Await 기본 구조
async 키워드는 function 앞에 붙임(function 앞에 async를 붙이지 않으면 syntax Error 발생)
await 키워드는 async 키워드가 붙어있는 함수 내부에서만 사용할 수 있음
기존 비동기 처리 방식
const printName = () => {
const getData = fetchData('abc.com/datas/3')
.then(data => {
if (data.id === 3) {
console.log(data.id);
}
})
}
async/await 방식
const printName = async () => {
const data = await fetchData('abc.com/datas/3');
if (data.id === 3) {
console.log(data.id);
}
}
▶ Async, Await 작동
await 키워드를 이용하면 awaited 하고 있는 값을 promise가 resolve할 때 까지 기다려서 비동기 함수를 일시정지 할 수 있음(promise의 resolve된 함수를 기다리기 위해서 then() 콜백을 한 것처럼 변수에 promise 값을 할당 할 수 있)
const one = () => new Promise((res, rej) => {
setTimeout(() => {
res('one!');
console.log('setTimeOut')
}, 10000);
})
async function myFunc() {
console.log('In function');
const res = await one();
console.log(res);
console.log('In function2');
}
console.log('Before function');
myFunc();
console.log('After function');
// 'Before function'
// 'In function'
// 'After After function'
// 10초후~!!
// 'setTimeOut'
// 'one!'
// 'In function2'
- console.log('Before function'); 실행
- myFunc() 함수가 호출
- console.log('In function'); 실행
- const res = await one();에서 one() 함수가 호출되어 Promise 반환
- setTimeout을 실행
- setTimeout의 정보(타이머, 콜백함수)가 Web API로 보내져 10초후 실행되도록 대기
- one 함수가 반환되고 await 키워드로 인해 비동기 함수가 완료될 때까지 대기
- my func 함수 실행이 일시 중지
- console.log('After function'); 실행
- Web API에서 타이머 완료 후 setTimeout 콜백이 매크로태스크(Macro Task) 큐에 추가
- 이벤트 루프는 콜스택이 비어있음을 확인하고 대기 큐의 첫번째 콜백을 실행
- res('one!') 실행. Promise가 완료되어 await one()이 해결됨
- console.log('setTimeOut') 실행
- const res = await one(); 완료되며 res는 one! 값을 가지게 됨
- console.log(res); 실행
- console.log('In function2'); 실행
▶ Async, Await 동작원리
제너레이터 함수는 next 메서드와 yield 표현식을 사용하여 함수 호출자와 함수의 상태를 주고 받을 수 있음
이러한 특징을 활용하면 Promise를 사용한 비동기 처리를 동기처럼 구현할 수 있음
즉, 프로미스의 후속 처리 메서드 없이 비동기 처리 결과를 반환하도록 구현할 수 있는 것
const async = (generatorFunc) => {
const generator = generatorFunc();
const onResolved = (arg) => {
const result = generator.next(arg);
return result.done ? result.value : result.value.then((res) => onResolved(res));
};
return onResolved;
};
async(function* fetchTodo() {
const url = '요청하는 url';
const response = yield fetch(url);
const todo = yield response.json();
console.log(todo);
})();
- async 함수가 호출되면 인수로 전달받은 제네레이터 함수 fetchTodo 호출하여 제네레이터 객체 생성
- → onResolved 함수는 상위 스코프의 generator 변수를 기억하는 클로저
- → async 함수가 반환한 onResolved 함수를 즉시 호출하여 const generator 에서 생성한 제네레이터 객체의 next 메서드 처음 호출
- → onResolved 함수 반환
- next 메서드 처음 호출되면 fetchTodo 의 첫 번째 yield 문까지 실행
- → 이터레이터 리절트 객체의 value 프로퍼티 값, 즉 첫 번째 yield 된 fetch(url) 함수가 반환한 프로미스가 resolve 한 Response 객체를 onResolved 함수에 인수로 전달하면서 재귀 호출
- → 이때 next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 false라면
- onResolved 함수에 인수로 전달된 Response 객체를 next 메서드에 인수로 전달하면서 next 메서드를 두 번째로 호출
- → 제네레이터 함수 fetchTodo 의 두 번째 yield 문까지 실행
- → 이때 next 메서드에 인수로 전달한 Response 객체는 제네레이터 함수 fetchTodo의 response 변수에 할당
- next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 false라면 이터레이터 리절트 객체의 value 프로퍼티 값인 response.json 메서드가 반환한 프로미스가 resolve 한 todo 객체를 onResolved 함수에 인수로 전달하며 재귀 호출
- onResolved 함수에 인수로 전달된 todo 객체를 next 메서드에 인수로 전달하면서 next 메서드를 세 번째로 호출
- → 이때 next 메서드에 인수로 전달한 todo 객체는 제네레이터 함수 fetchTodo의 todo 변수에 할당되고 제네레이터 함수 fetchTdoo 가 끝까지 실행
- next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true라면
- → 이터레이터 리절트 객체의 value 프로퍼티 값, 즉 제네레이터 함수 fetchTodo의 반환값인 undefined를 그대로 반환하고 처리 종료
결론적으로 yield 표현식을 할당받는 변수에 값이 할당되는 시점은 yield 표현식이 끝나는 시점이라고 정리할 수 있음
async function fetchTodo() {
const url = '요청 url';
const response = await fetch(url);
const todo = await response.json();
console.log(todo);
}
yield 표현식이 await 키워드로 바뀌었음
await 키워드 뒤에 있는 일을 끝날 때 까지 기다린 뒤 변수에 할당하는 것
Babel을 통해 위 예시 코드를 ES5 버전으로 변환해보면 generator를 활용한 코드와 비슷한 형식을 볼 수 있음
더 정확히 말하면 await 키워드는 Promise가 Settled(처리된) 상태가 될 때까지 대기하다가 Settled 상태가 되면 Promise가 resolve한 처리 결과를 반환함 따라서 반드시 Promise 객체 앞에서 사용해야함
- Babel: 입력과 출력이 모두 자바스크립트 코드인 컴파일러
최신 버전의 자바스크립트가 실행되지 않는 구버전의 브라우저에서도 정상적으로 실행되도록 변환해줌
Reference