[JS] async와 await
📌 참고 사이트
async와 await (javascript.info)
async와 await
ko.javascript.info
📋 async 함수
async는 function 앞에 위치하고, 항상 Promise를 반환한다.
코드에서 Promise가 아닌 값을 반환하도록 작성하였어도 이행 상태의 Promise(resolved promise)로 값을 감싸 이행된 Promise가 반환되도록 한다.
// Promise가 아닌 값 반환하기 (async를 붙인 함수이므로 Promise로 감싸서 반환됨)
async function f() {
return 1;
}
f().then(console.log); // 1
// Promise를 명시적으로 반환하기
async function f() {
return Promise.resolve(1);
}
f().then(console.log); // 1
📋 await
await은 async 함수 안에서 사용하며, Promise가 처리될 때까지 기다리고 결과는 그 이후에 반환된다.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('완료!'), 1000)
});
let result = await promise; // await을 사용하였으므로 Promise가 이행될 때까지 기다린다.
alert(result);
}
f(); // 완료! (1초 뒤에 출력됨)
위 예제의 경우, 함수를 호출하고 함수 본문이 실행되는 도중에 await가 있는 줄에서 실행이 잠시 중단되었다가 Promise가 처리되면 실행이 재개된다.
이때 Promise 객체의 result값(resolve된 혹은 reject된 값) 이 변수 result에 할당된다.
결과적으로 1초 뒤에 '완료!'가 출력된다.
💡 await를 사용해 프라미스가 처리되길 기다리는 동안에는 엔진이 다른 일(다른 스크립트를 실행하거나 이벤트 처리를 하는 등)을 할 수 있기 때문에 CPU 리소스가 낭비되지 않는다. 또한 promise.then보다 가독성이 더 좋다는 장점이 있다.
💡 async와 await은 Promise.all과도 함께 쓰는 것이 가능하다.
📋 Promise 코드를 async, await 코드로 변환해 보기
💻 Promise 코드
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithunUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// 위에서 만든 함수를 이용하여 Promise 체이닝을 만들어 비동기 작업을 순차적으로 실행
loadJson('/article/promise-chaning/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// .....
💻 async, await 코드
async function showAvatar() {
// JSON 읽기
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// github 사용자 정보 읽기
let githunResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// 아바타 보여주기
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = 'promise-avatar-example';
document.body.append(img);
// 3초 대기
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
// 3초 대기 후 실행
img.remove();
return githubUser;
}
showAvatar();
async await을 사용한 코드가 Promise 체이닝을 사용한 코드보다 훨씬 더 직관적이고 깔끔하다.
📋 에러 핸들링
Promise가 정상적으로 이행되면 await promise는 Promise 객체의 result에 저장된 값을 반환한다.
Promise가 거부되면 에러가 던져지게 된다.
// 아래의 두 코드는 동일한 작업을 한다.
async function f() {
// 실제 상황에서는 Promise가 거부(reject)되기 전에 시간이 약간 지체되어
// await가 에러를 던지기 전에 지연이 발생할 수 있다.
await Promise.reject(new Error('에러 발생!'));
}
async function f() {
throw new Error('에러 발생!');
}
await가 던진 에러는 try ... catch 문을 사용하여 잡을 수 있다.
async function f() {
try {
let response = await fetch('http://유효하지-않은-주소');
let user = await response.json();
} catch(err) {
// fetch와 response.json에서 발생한 에러 모두(try 안에서 발생한 에러)를 catch에서 잡는다.
alert(err);
}
}
f(); // TypeError: Failed to fetch
try ... catch를 작성하지 않으면 async 함수를 호출해 만든 Promise가 거부 상태가 된다.
이럴 때에는 .catch를 추가하면 거부된 Promise 처리가 가능하다.
(.catch조차 추가하지 않으면 처리되지 않은 Promise 에러가 발생한다.)
async function f() {
let response = await fetch('http://유효하지-않은-주소');
}
// f()는 거부 상태의 Promise가 된다.
f().catch(alert); // TypeError: failed to fetch
💡 async와 await를 사용하면 await가 대기 처리를 해주므로 프로미스를 사용할 때처럼 .then과 .catch가 거의 필요하지 않다. (try와 catch 사용 가능) but .then과 .catch를 추가해 줌으로써 최종 결과나 처리되지 못한 에러를 다룰 수 있다.
✏️ 과제
📍 async과 await을 사용하여 코드 변경하기 1
아래 .then / catch 를 이용한 코드 대신 async / await 를 이용한 코드로 변환해 보자.
💻 then / catch 코드
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
})
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404
💻 async / await 코드
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
let json = await response.json();
return json;
}
throw new Error(response.status);
}
loadJson('https://jsonplaceholder.typicode.com/todo/1').catch(alert); // Error: 404
📍 async가 아닌 함수에서 async 함수 호출하기
일반 함수가 하나 있는데, 여기에서 async 함수를 어떻게 하면 호출하고, 그 결과를 사용할 수 있을까?
async function wait() {
await new Promise(resolve => setTimeout(resolve, 1000));
return 10;
}
function f() {
// 코드 작성
// async wait() 함수를 호출하고 그 결과인 10을 얻을 때까지 기다리려면 어떻게 해야 할까?
// 주의! f는 일반 함수이므로 여기에서는 'await'를 사용할 수 없다.
}
🅰️
function f() {
wait().then(result => alert(result));
}
f(); // 10 (1초 뒤에 출력된다.)
async 함수를 호출하면 Promise가 반환되므로 then 메서드를 사용해 그 Promise의 결괏값(resolve의 결괏값)을 받아 보여주면 된다.
📍 async과 await을 사용하여 코드 변경하기 2
.then / .catch를 이용한 다음 코드를 async / await을 이용한 코드로 변경해 보자.
💻 then과 catch를 이용한 코드
class HttpError extends Error {
constructor(response) {
super(`${response} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response =>
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
// 유효한 사용자를 찾을 때까지 반복해서 username을 물어본다.
function demoGithubUser() {
let name = prompt('Github username을 입력하세요.', 'iliakan');
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`이름: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert('일치하는 사용자가 없습니다. 다시 입력해 주세요.');
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
💻 async와 await을 이용한 코드
async function demoGithubUser() {
let user;
while (true) { // loadJson이 제대로 실행되기 전까지 이름 물어보고 정보 가져오기 시도 반복
let name = prompt('GitHub username을 입력하세요.', 'iliakan');
try {
user = await loadJson(`https://api.github.com/users/${name}`);
break; // 에러가 없는 경우 반복문 빠져나옴
} catch(err) {
if (err instanceof HttpError && err.response.status == 404) {
// 아래 alert 창이 뜬 이후에도 반복문은 계속 돈다.
alert('일치하는 사용자가 없습니다. 다시 입력해 주세요.');
} else {
// 알 수 없는 에러(사용자 불일치 외 에러)는 다시 던져진다.
throw err;
}
}
}
alert(`이름: ${user.name}.`);
return user;
}
demoGithubUser();