async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다.
기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와 준다.
마치 책이라도 읽듯이 위에서 부터 아래로 차근 차근 읽으면서 사고할 수 있는 그런 코드라고 생각된다.
예를 들어,
var user = {
id : 1,
name : "Hong"
};
if(user.id === 1) {
console.log(`name = ${user.name}`); // name = Hong
}
와 같은 코드를 말할 수 있다.
다음의 코드를 보도록 하자.
function logFetchUser(findId) {
var user = fetchUser(`/api/users/${findId}`); //http 통신 코드라 가정
if(isUser(user.id, findId))
console.log(user.name);
}
function isUser(userId, findId) {
if(userId === findId)
return true;
return false;
}
fetchUser()
라는 메서드를 호출하면 HTTP 통신을 통해 서버에서 사용자 정보를 가져온다고 가정한다.
여기서 아래와 같이 async
와 await
를 추가해 보자.
async function logFetchUser(findId) {
var user = await fetchUser(`/api/users/${findId}`);
if(isUser(user.id, findId))
console.log(user.name);
}
function isUser(userId, findId) {
if(userId === findId)
return true;
return false;
}
이런 방식으로 async
와 await
를 추가할 수 있다.
이제부터 살펴보도록하자.
위에서 본 코드를 먼저 살펴보자.
function logFetchUser(findId) {
var user = fetchUser(`/api/users/${findId}`); //http 통신 코드라 가정
if(isUser(user.id, findId))
console.log(user.name);
}
function isUser(userId, findId) {
if(userId === findId)
return true;
return false;
}
fetchUser
는 서버에서 데이터를 받아오는 HTTP 통신 코드라고 가정하였다.
일반적으로 HTTP 통신 코드는 자바스크립트에서는 비동기적으로 코드를 처리하게 된다.
즉, 위와 같은 코드는 콜백 함수, 프로미스 등을 사용하여야만 실행 순서를 보장 받을 수 있다.
만약, 실행순서를 보장받지 못한다면 ??
console.log(user.name)
이 fetchUser()
보다 호출 스택으로부터 먼저 호출이 되어 undefined 를 출력하게 될지도 모릅니다.
function logFetchUser(findId) {
var user = fetchUser(`/api/users/${findId}`, (user) => {
if(isUser(user.id, findId))
console.log(user.name);
});
}
function isUser(userId, findId) {
if(userId === findId)
return true;
return false;
}
위와 같이 콜백으로 비동기 처리 코드를 작성할 수도 있다. BUT, 콜백은 콜백지옥을 야기할 수 도 있을 뿐만 아니라 위에서 봤었던 개발자가 읽기 좋은 코드 와는 사뭇 다름을 확인할 수 있다.
이를 async
와 await
를 사용하여 해결할 수 있다.
async function logFetchUser(findId) {
var user = await fetchUser(`/api/users/${findId}`);
if(isUser(user.id, findId))
console.log(user.name);
}
function isUser(userId, findId) {
if(userId === findId)
return true;
return false;
}
앞에서 설명했듯이, 비동기적 코드가 사용될 함수의 function
앞에는 async
를 붙이면 되고 비동기적 코드 fetchUser()
앞에는 await
를 붙이면 된다.
결론적으론, async
와 await
를 사용함으로서 개발자가 읽기 좋은 코드 와 실행 순서의 보장 을 이뤄낼 수 있다.
async
와 await
에 대해 기본 문법을 좀 더 자세히 봐보자.
function
앞에 async
를 붙이면 해당 함수는 항상 promise
를 반환한다. promise
가 아닌 값을 반환하더라도 이행 상태의 promise
로 값을 감싸 이행된 promise
가 반환되도록 한다.
async function foo() {
return 1;
}
foo().then((item) => {
console.log(item); // 1
});
위의 코드를 실행해 보면 1이 출력되는 것을 확인할 수 있다.
즉, async
가 붙은 함수는 반드시 promise
를 반환하고, promise
가 아닌 것은 promise
로 감싸 반환한다.
await
는 async
가 붙은 function
에서만 사용 가능하다. 자바스크립트는 await
키워드를 만나면 promise
즉, 비동기 처리 코드가 수행 완료될 때 까지 기다린다.
결과는 그 다음에 반환된다.
async function bar() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("setTimeOut Finished!"), 1000);
});
let result = await promise;
console.log(result); // setTimeOut Finished!
}
함수를 호출하고, 함수 본문이 실행되는 도중에 await
처리된 줄에서 잠시 1초간 중단되었다가 promise
가 종료되면 실행이 재개되어 콘솔에 출력이 된다.
await
를 하는 동안, 엔진이 다른 일을 처리할 수 있기 때문에, CPU 리소스를 낭비하지 않을 수 있다.
await
는 최상위 레벨 코드에서 작동하지 않는다.
let user = await fetchUser(
/api/users/1); // Error Occured!
이와 같은 오류는 익명 async
함수로 감싸면 해결할 수 있다.
(async () => {
let user = await fetchUser(`/api/users/1`); // Error Occured!
}) ();
await
는 thenable
객체를 받는다.
await
에도 then
메서드가 있는 호출 가능한 객체를 사용할 수 있다.
class ThenableClass {
constructor() {}
then(resolve, reject) {
resolve("Hello");
}
}
async function thenableFunction() {
let res = await new ThenableClass();
}
클래스 메서드에도 async
를 사용할 수 있다.
class AsyncMethod {
async test() {
return await new Promise((resolve, reject) => {
resolve("test");
});
}
}
promise
가 거부되면 throw
문과 같이 에러가 던져진다.
실제 상황에선 promise
가 거부 되기 전에 약간의 시간이 지체되는 경우도 있다. 이 땐, await
가 에러를 던지기 전에 지연이 발생한다.
await
가 던진 에러는 try catch
를 사용해 잡을 수 있다.
async function awaitErrorEx() {
try {
let user = await fetchUser(`/api/users/1`);
} catch(err) {
console.error(`Error Occured! : ${err.message}`);
}
}
또한, .catch
를 통해서도 가능하다.
async function awaitErrorEx() {
let user = await fetchUser(`/api/users/1`);
}
awaitErrorEx()
.catch((err) => {
console.error(`Error Occured! : ${err.message}`);
}
);
async
와 await
는 Promise.all
과도 함께 쓸 수 있다.
여러 개의 promise
가 모두 처리되길 기다려야 하는 상황이라면 Promise.all
로 감싸 await
를 붙여 사용할 수 있다.
async function test() {
let results = await Promise.all([
fetchUser(url1),
fetchUser(url2),
fetchUser(url3)
...
]);
};
지금 까지 async
와 await
에 대해서 살펴보았다.
살펴봤던 내용을 요약하여 보자.
비동기적 코드의 실행 순서의 보장을 이뤄낼 수 있다.
개발자가 읽기 좋은 코드를 유지하며 개발이 가능하다.
async
함수는 언제나 promise
를 반환한다.
await
키워드를 붙이면 자바스크립트는 promise
가 처리될 때까지 대기한다.