callback
패턴의 여러 단점을 해결한 Promise
또한 결과적으로는 비동기 처리이므로, 동기식 코드와 섞어서 사용하면 코드의 흐름을 파악하기 어렵다는 단점이 있다.
하지만 ES 2017(ES8)
에서 추가된 async/await 키워드를 사용하면 비동기 처리도 동기식 흐름처럼 보이게 할 수 있다.
두 키워드가 어떤 기능을 하는지 예제를 통해 자세히 알아보자!😆
async
키워드는 함수 앞에 붙일 수 있는 키워드이다. 일반적인 함수는 물론이고 익명 함수의 앞에도 사용할 수 있다.
함수의 앞에 async
키워드를 붙이게 되면, 해당 함수가 반환하는 값은 항상 Promise 객체가 된다.
무슨 의미인지 아래 예제를 보도록 하자!
const func = async function() {
return 'Im function';
};
console.log(func()); // Promise { 'Im function' }
func().then(str => console.log(str)); // Im function
func()
함수를 호출한 결과 문자열 'Im function' 을 반환하는 것이 아니라, Promise.resolve('Im function')
으로 fulfilled
상태가 된 Promise
객체를 반환하는 것을 볼 수 있다.
이렇게 async
키워드를 사용하면 기본형 데이터를 반환하더라도 Promise
객체로 래핑해서 반환한다는 것을 알 수 있다.
그럼 이 기능을 어디에다 써먹느냐. 바로 async 키워드를 사용한 함수는 내부에서 await 키워드를 사용할 수 있다. 는 점이다.
async/await
키워드를 묶어서 설명하는 이유는, await
키워드는 단독으로 사용할 수 없고 반드시 async
키워드가 붙은 함수 내부에서만 사용할 수 있기 때문이다.
또한 await
키워드는 Promise 객체를 반환하는 함수 에만 사용할 수 있으므로, Promise
객체를 정확히 이해하는 것이 선행된다.
그럼 await
키워드는 어떤 역할을 하는지 예제를 통해 알아보자!🙃
delayPrint();
function delayPrint() {
let str = 'string';
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('async data!'), 2000);
});
promise.then(data => str = data);
console.log(str); // 'string'
}
Promise
역시 비동기 처리이므로 delayPrint()
함수를 호출한 결과는 동기식 코드가 먼저 처리된다.
따라서 then()
메서드를 이용해 str = 'async data!'
를 실행하고 console.log()
함수로 출력해주려고 했지만, 원하는 결과를 얻을 수 없다.
위 코드를 async/await
를 활용한 코드로 바꾸면 어떻게 되는지 보도록 하자!
delayPrint();
async function delayPrint() {
let str = 'string';
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('async data!'), 2000);
});
str = await promise;
console.log(str); // 'async data!'
}
놀랍게도 동기식 코드와 비동기식 코드가 섞여 있는데도 불구하고 정상적으로 동작한다!😨
이유는 await
키워드가 붙여진 Promise
는 settled
되기 전까지 다음 코드를 실행하지 않고 기다리기 때문이다.
'그럼 동기식 코드와 다를 바 없는 것이 아닌가?' 라고 생각할 수 있지만, await
키워드를 이용해 대기하고 있는 Promise
는 여전히 비동기 처리이기 때문에, 메인 스레드가 멈추는 걱정을 하지 않아도 된다.
이 말이 사실인지 delayPrint()
함수를 호출하기 전후로 동기식 코드를 추가해보자!
console.log('Im outer code! before delayPrint()');
delayPrint();
console.log('Im outer code! after delayPrint()');
async function delayPrint() {
let str = 'string';
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('async data!'), 2000);
});
str = await promise;
console.log(str);
}
실행 결과
1. Im outer code! before delayPrint()
2. Im outer code! after delayPrint()
3. delay 2s
4. async data!
delayPrint()
함수는 정확히 비동기식으로 실행되며, 함수 내부에서는 동기식 흐름을 가진다는 것을 알 수 있다.
그리고 await
키워드를 사용해서 동기식 프로그램처럼 만들면, 예외 처리도 손쉽게 가능하다는것 또한 장점이다.
Promise
는 catch()
메서드를 이용해서 에러에 대한 처리를 진행했다.
하지만 async/await
를 사용하면 비동기 코드에서도 마치 동기식 코드처럼 try...catch
문법을 이용해서 에러 핸들링이 가능하다!
파일을 불러오는 함수를 async/await
를 사용해서 작성해보자.
const fs = require('fs');
getFileContent()
async function getFileContent() {
let str = 'original';
const promise = new Promise((resolve, reject) => {
fs.readFile('./text_1.txt', (err, data) => {
if(err) return reject(err);
else if(data.toString() === '') return reject('no contents');
resolve(data.toString());
});
});
try {
str = await promise;
console.log(str);
} catch(err) {
console.error(err);
}
}
async/await
키워드를 사용하면 위의 코드처럼 비동기 코드에서도 try ... catch
문법을 이용해서 훨씬 이해하기 쉬운 에러 처리가 가능해진다.
이처럼 async/await
키워드는 callback
패턴 혹은 Promise
패턴만 사용했을 때 보다 훨씬 사람이 읽기 쉬운 코드를 만들 수 있도록 도와주지만, 늘 그렇듯 구형 브라우저에서는 지원하지 않는 경우가 있다.
사용하기 전에 async/await
를 지원하는 브라우저인지 꼭 확인하고, 만약 그렇지 않다면 Babel
같은 트랜스파일러를 이용하도록 하자!
모자란 부분이나 틀린 내용이 있다면 지적해주시면 감사하겠습니다!!🙂
참고 자료
async와 await를 사용하여 비동기.. | MDN
https://developer.mozilla.org/ko/docs/Learn/JavaScript/Asynchronous/Async_await
async function | MDN
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function
await | MDN
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/await
async와 await
https://ko.javascript.info/async-await