ES6+ 향상된 비동기 프로그래밍(3) - async await
-. async await는 비동기 프로그래밍을 동기 프로그래밍처럼 작성할 수 있도록 함수에 추가된 기능이다.
-. Promise가 javascript의 표준이 되고 2년 후(ES2017)에 async await도 표준이 되었다.
-. Promise가 async await 보다 큰 개념이며, async await를 사용해 then 메서드 chain 보다 가독성을 높여줄 수 있다.
1. async await 이해하기
#1 async await 함수는 프로미스를 반환한다.
Promise = 객체
async await = 함수에 적용되는 개념
Promise를 반환하는 async await 함수
async function getData() {
return 1234; // 1번
}
getData().then(data => console.log(data)); // 1234
async await 함수 내부에서 반환하는 값이 Promise라면 그 객체를 그대로 반환한다.
예외 발생 시에는 rejected(거부됨) 상태인 Promise가 반환된다.
async function getData() {
throw new Error('1234');
}
getData().catch(error => console.log(error));
// 에러 발생: 1234
#2 await 키워드를 사용하는 방법
await 키워드는 async 함수 내부에서만 사용할 수 있다.
만약 await 키워드 오른쪽에 Promise를 입력하면 그 Promise가 settled(처리됨) 상태가 될 때까지 기다린다.
따라서 await 키워드를 사용하면 비동기 처리를 기다리면서 순차적으로 코드를 작성할 수 있다.
await 키워드 사용 예
function requestData(value) {
return new Promise(resolve => {
setTimeout(() => {
console.log('requestData: ', value);
resolve(value);
}, 1000);
});
}
async function getData() {
const data1 = await requestData(1); // 1번
const data2 = await requestData(2); // 2번
console.log(data1, data2); // 3번
return [data1, data2];
}
getData();
// requestData: 1
// requestData: 2
// 1 2
1번, 2번의 함수 requestData가 반환하는 Promise가 settled(처리됨) 상태가 될 때까지
3번의 코드는 실행 되지 않는다.
#3 async await는 Promise 보다 가독성이 좋다
async await 와 Promise 코드 비교
function getDataPromise() { // 1번
asyncFn1()
.then(data => {
console.log(data);
return asyncFn2();
})
.then(data => {
console.log(data);
})
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
console.log(data1);
const data2 = await asyncFn2();
console.log(data2);
}
1번은 Promise로 작성한 함수
2번의 async await 함수는 then 메서드를 호출할 필요가 없기 때문에 가독성이 좋아진다.
비동기 함수 간의 의존성이 높아질수록 async await의 가독성이 좋아지는 것을 볼 수 있다.
아래에서 asyncFn1, asyncFn2, asyncFn3 이 세 함수는 각각의 반환값을 다른 함수의 인수로 넣는 의존성을 가지고 있디.
function getDataPromise(){ // 1번
return asyncFn1()
.then(data1 => Promise.all([data1, asyncFn2(data1)]))
.then(([data1, data2]) => {
return asyncFn3(data1, data2);
});
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
const data2 = await asyncFn2(data1);
return asyncFn3(data1, data2);
}
1번에서는 asyncFn3 함수에 인수를 전달하기 위해 Promise.all을 사용했다.
2번에서 async await 함수는 복잡한 의존성을 직관적이게 표현했다.
2. async await 활용하기
#1 비동기 함수를 병렬로 실행하기
async await 함수에서 순차적으로 실행하는 코드
async function getData() {
const data1 = await asyncFn1();
const data2 = await asyncFn2();
//...
}
위의 두 함수 사이에 의존성이 없다면 동시에 실행하는 것이 좋을 것이다.
Promise는 생성과 동시에 비동기 코드가 실행된다.
그래서 아래와 같이 두개의 Promise를 생성하고 await 키워드를 나중에 사용하여 병렬 코드를 만들어 보자.
async function getData() {
const promise1 = asyncFn1();
const promise2 = asyncFn2();
const data1 = await promise1;
const data2 = await promise2;
//...
}
두개의 프로미스가 생성되고 각자의 비동기 코드가 실행된다.
이 두 프로미스가 생성된 후 await 키워드를 사용해 기다리기 때문에 두 개의 비동기 함수가 병렬로 처리된다.
아래와 같이 Promise.all을 사용하면 더 간결한 코드를 작성할 수 있다.
async function getData() {
const [data1, data2] = await Promise.all(asyncFn1(), asyncFn2());
//...
}
#2 예외 처리
async await 함수 내부에서 발생하는 예외는 아래와 같이 try catch 문으로 처리할 수 있다.
async function getData(){
try {
await getAsync(); // 1번
return getSync(); // 2번
} catch (error) {
console.log(error);
}
}
1번(비동기 함수)과 2번(동기 함수)에서 발생하는 모든 예외가 catch 문에서 처리 된다.
여기서 getData 함수가 async await 함수가 아니였다면 1번의 getAsync 함수에서 발생하는 예외는 catch 문에서 처리 되지 않는다. 이유는 getAsync 함수의 처리가 끝나는 시점을 알 수 없기 때문이다.
#2 Thenable
asycn await는 ES6의 Promise가 아니더라도 then 메서드를 가진 객체를 Promise 취급한다.
이렇게 Promise가 아닌데 then 메서드를 가진 객체를 Thenable이라고 부른다.
async await 함수에서 Thenable을 사용한 예
class ThenableSample {
then(resolve, reject) { // 1번
setTimeout(() => resolve(1234), 1000);
}
}
async function asyncFn() {
const result = await new ThenableSample(); // 2번
console.log(result); // 1234
}
1번 : then 메서드를 가지고 있어 new ThenableSample() 객체는 Thenable이다.
2번 : async await 함수는 Thenable도 Promise 처럼 처리한다.