Promise객체로 인해 callback hell문제를 해결하고, 비동기 실행코드를 보기좋게 작성하는 것이 가능해졌다.
그럼에도 자바스크립트 문법은 계속 발전해서 Promise객체를 더 편하게 다룰 수 있는 문법이 생겨났고
그것이 바로 async/await이다.
// fetch의 경우
fetch('https://learn.codeit.kr/api/members/1')
.then((response) => response.text())
.then((result) => {console.log(result);})
// async/await의 경우
async function fetchAndPrint(){
const response = await fetch('https://learn.codeit.kr/api/members/1');
const result = await response.text();
console.log(result);
}
fetchAndPrint();
위 코드의 fetch와 async/await은 같은 기능을 한다.
async/await의 경우를 보면, 함수의 앞에는 async가, promise객체를 return하는 함수 앞에는 await 가 붙어있다.
async
- asynchronous 의 줄임말.
- 함수 안에 비동기적으로 실행되는 부분이 있다는 것을 알려준다.
- 비동기적으로 실행될 부분이란, 위 예시에서 await 오른편으로 작성된 로직이다.
await
- Promise객체를 리턴하는 코드의 앞에 붙여서, await뒤의 코드를 수행한 이후 리턴된 Promise객체가 settled될 때 까지 기다린다.
- settled되면 그 결과의 response객체를 추출해서 리턴한다.
- async 함수의 안에서만 사용 가능하다.
async가 붙어있는 함수 안에 비동기 실행을 하는 부분이 있다 = async가 붙어있는 함수 안에 Promise객체를 리턴하는 코드가 있다.
async/await구문의 실행 원리
async function fetchAndPrint(){
console.log(2);
const response = await fetch('https://learn.codeit.kr/api/members/1');
console.log(7);
const result = await response.text();
console.log(result);
}
console.log(1);
fetchAndPrint();
console.log(3);
console.log(4);
console.log(5);
console.log(6);
위 코드를 실행해보면, 숫자 순서대로 출력된다.
그 실행의 순서는 아래와 같다.
- console.log(1)
- fetchAndPrint함수의 console.log(2)
- await를 만나(fetch() 앞) 함수밖으로 실행 순서 이동
- console.log(3)
- console.log(4)
- console.log(5)
- console.log(6)
- fetch() 완료
- console.log(7)
- await(text() 앞)를 만나 함수 밖으로 실행 순서 이동
- 하지만 함수밖에 실행할 로직이 없다.
- response.text()완료
- console.log(result);
첫번째 await을 만나고, 실행순서가 함수 밖으로 이동하면서 함수밖의 로직들이 콜스택에 쌓여있기때문에
fetch작업이 먼저 끝났더라고 함수 밖 로직이 끝나기 전까지는 함수 내부로직으로 다시 돌아갈 수 없다.
async함수는 함수밖의 코드들이 실행되고 나서 또 실행될 코드들이 남아있다는 것을 의미한다.
그리고 함수밖의 코드들이 실행되고 나서 실행될 코드들이 바로 await이 붙은 로직들을 의미한다.
async/awiat함수는 마치 동기 함수처럼 생겼는데,
구문 자체가 기존의 Promise chaining을
- 개발자가 더 편하게 작성할 수 있도록 하기 위해서,
- 코드의 가독성을 높이기 위해서
도입된 일종의 Syntatic sugar(기존 문법을 더 편하게 사용할 수 있도록 하는 문법적 장치)에 해당하기 때문이다.
우리는 async/await문법으로 Promise객체를 우리에게 익숙한 동기 실행 코드 방식으로 다룰 수 있게 되었다.
동기 실행 코드처럼 생겼으나, async/await은 비동기로 실행되며 promise객체를 다룬다는 것을 기억하자.
async / await 에서의 에러 처리 방법
Promise객체에서는 catch메서드를 사용했었는데, async / await에서는 try-catch문을 사용한다.
async function fetchAndPrint(){
try {
const response = await fetch('https://learn.codeit.kr/api/members/1');
const result = await response.text();
console.log(result);
} catch (error){
console.log(error);
}
}
promise객체의 상태가 rejected가 되는 순간, catch문으로 이동한다.
finally문도 추가할 수 있다.
async function fetchAndPrint(){
try {
const response = await fetch('https://learn.codeit.kr/api/members/1');
const result = await response.text();
console.log(result);
} catch (error){
console.log(error);
} finally {
console.log('exit');
}
}
async / await 의 리턴값
async함수는 항상 promise객체를 리턴한다.
async function fetchAndPrint(){
return 3;
}
위 코드는 async함수이고 숫자 3을 리턴중임에도
작업성공결과로 숫자 3을 가지는 fulfilled상태의 promise객체를 리턴하게된다.
이러한 결과는 then메서드의 리턴값과 유사하다.
아주 똑같지만 아래와 같이 정리해본다.
1. promise객체를 리턴
async function fetchAndPrint() {
return Promise.resolve('Success');
}
fetchAndPrint();
- async함수 안에서 promise객체를 리턴하는 경우에는 해당 Promise객체와 동일한 상태과 결과를 가지는 promise객체를 리턴한다.
2. promise객체가 아닌 값을 리턴
async function fetchAndPrint() {
return 3;
}
fetchAndPrint();
- 숫자, 문자열, 배열, 객체 등 promise객체가 아닌 다른 값을 리턴하는 경우, fulfilled상태이면서 리턴시킨 값을 작업성공결과로 가지는 promise객체를 리턴한다.
3. 아무 값도 리턴하지 않음
- fulfilled상태이면서, undefined를 작업 성공결과로 가지는 promise객체를 리턴한다.
4. async함수 내부에서 에러가 발생
- rejected상태이면서, 에러객체를 작업 실패 정보로 가지는 promise객체를 리턴한다.
asyn함수 안에서의 async함수 사용
- 아래 예제는 사용자의 address와 phone프로퍼티를 제외한 나머지 정보만을 가지고 오는 코드이다.
- async함수 안에서 async함수를 사용하고있다.
- async함수는 항상 promise객체를 리턴하기때문에 그 앞에 await을 붙여서 사용할 수 있다.
- async함수안의 async함수안에서도 async함수를 사용할 수 있다.
const applyPrivacyRule = async function (users) {
const resultWithRuleApplied = users.map((user) => {
const keys = Object.keys(user);
const userWithoutPrivateInfo = {};
keys.forEach((key) => {
if(key !== 'address' && key !== 'phone'){
userWithoutPrivateInfo[key] = user[key];
}
});
return userWithoutPrivateInfo;
});
// 작업이 오래걸리는 예시를 들기위해 setTimeout함수 사용했다.
// 아래의 프로미스객체를 리턴한다고 봐도 된다.
const p = new Promise((resolve, reject) => {
setTimeout(() => { resolve(resultWithRuleApplied); }, 2000)
});
return p;
}
async function getUesers() {
try{
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const result = await response.text();
const users = JSON.parse(result);
const resultWithPrivacyRuleApplied = await applyPrivacyRule(users);
return resultWithPrivacyRuleApplied;
} catch (error) {
console.log(error);
} finally {
console.log('exit');
}
}
getUesers().then((result) => { console.log(result); });
getUsers라는 async함수안에서, applyPrivacyRule이라는 async함수를 사용하고 있는 모습을 볼 수 있다.
async를 붙이는 위치
// Function Declaration
async function example1(a, b) {
return a + b;
}
// Function Expression(Named)
const example2_1= async function add(a, b) {
return a + b;
};
// Function Expression(Anonymous)
const example2_2 = async function(a, b) {
return a + b;
};
// Arrow Function
const example3_1 = async (a, b) => {
return a + b;
};
// Arrow Function(shortened)
const example3_2 = async (a, b) => a + b;
// 즉시실행함수
(async function (a, b) {
return a + b;
}(1, 2));
// 즉시실행함수(shortened)
(async (a, b) => a + b)(1, 2);
- function키워드가 있으면 키워드 앞에, 없으면 파라미터 앞에 작성
async 함수를 작성할 때 주의해야할 성능 문제
async function getResponses(urls) {
for(const url of urls){
const response = await fetch(url);
console.log(await response.text());
}
}
- 위 코드는 순서대로 response가 console에 찍힌다.
- await를 만나면서 작업이 중단되기때문이다.
- 만약 순서 상관 없이 찍히기만되는 로직을 짜려했다면 위 코드는 성능면에서 아쉬운 코드가 되겠다.
- 아래와 같이 수정가능하다.
async function fetchUrls(urls){
for(const url of urls){
(async () => {
const response = await fetch(url);
console.log(await response.text());
})();
}
}
- 위 코드는 async를 붙인 즉시실행함수를 for문안에 넣었다.
- 이러면 await를 만나면서 함수 밖으로 실행순서가 이동되고, 밖의 로직을 먼저 진행시키다보니 결국 작업이 먼저 끝난 response.text부터 콘솔에 찍히게 된다.
- 순차적인 처리가 필요한게 아니라면, 이처럼 각 작업을 async함수로 묶어주면 된다.
정리
비동기 실행의 의미
- 특정 작업이 시작되고, 그 작업이 모두 완료되기 전에 바로 다음 코드가 실행되는 방식의 실행, 나머지 작업은 나중에 콜백을 통해 수행되는 방식의 실행
- 특정 처리를 나중으로 미루는 방식의 실행
- 콜백을 등록해두고, 추후에 특정 조건이 만족되면 그 콜백으로 나머지 작업을 수행하는 방식의 실행
비동기실행 관련 문법사용에 주의점
- 콜백을 함수의 파라미터로 바로 전달하는 전통적인 방식의 비동기 실행 함수들 중에서도 setInterval, addEventListener처럼 그 콜백이 여러 번 실행되어야 하는 것들은 Promisify하면 안된다. Promise 객체는 한번 settled되고나면 그 상태와 결과가 다시는 바뀌지 않기 때문.
- async/await 구문의 경우, await은 async 함수 안에서만 사용할 수 있고, 코드의 top-level(어떤 함수 블록 안에 포함된 것이 아닌 코드의 최상위 영역)에서는 사용될 수 없다. 그래서 코드의 top-level에서 async 함수가 리턴한 Promise 객체의 작업 성공 결과를 가져오려면 await을 사용할 수는 없고, 여전히 then 메소드를 사용해야한다. >> 이게 무슨말일까?? 찾아봐야겠다...
'JavaScript' 카테고리의 다른 글
JavaScript - ajax (1) | 2023.10.15 |
---|---|
JavaScript - 비동기실행 순서 예제 (1) | 2023.10.15 |
JavaScript - 비동기 실행과 promise객체 (0) | 2023.10.13 |
JavaScript - API(response data, REST API, contnet-type 등) (0) | 2023.10.13 |
JavaScript - 웹과 통신 (0) | 2023.10.13 |