# Promise를 통한 비동기 코딩
먼저 Promise를 통해서 어떻게 비동기 처리를 하는지 간단하게 리뷰해보자.
어떤 원격 REST API를 호출하여 게시물 작성자의 이름을 return하는 함수를 작성하고, 그 함수를 호출해보자.
function fetchAuthorName(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((response) => response.json()) //resolve()함수의 인자로 넘겨받은 값이 response가 된다.
.then((post) => post.id) //위 return한 response.json()값이 post가 된다.
.then((userId) => { //위 return한 post.id가 콜백함수의 userId가 된다.
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((response) => response.json()) //위 fetch함수의 return값인 Promise객체의 resolve함수의 인자값
.then((user) => user.name); //위 response.json()값이 user이 된다.
});
}
fetchAuthorName(1).then((name) => console.log("name:", name)); //위 user.name값이 name이 된다.
name: Leanne Graham
브라우저 내장함수인 fetch()를 호출하여 Promise 객체를 리턴받은 후에, Method chaining기법을 이용하여 then() 메서드를 연쇄적으로 호출하고 있다.
then() 메서드는 바로 이전 then() 메서드의 출력값을 입력값으로 사용하여 새로운 출력값을 만들고, 또 바로 다음 then() 메서드의 입력값으로 넘겨준다.
fetchAuthorName() 함수 자체도 결국 게시물 작성자의 이름을 얻을 수 있는 Promise 객체를 return하기 때문에 then() 메서드를 사용하여 게시물 작성자의 이름을 출력해야 한다.
# 문제점
Promise를 사용하면 then함수를 여러번 호출하는 method chaining을 이용해 비동기처리를 하는 경우가 많은데, 여기에는 여러가지 문제점이 있다.
1/ 디버깅의 어려움
위 코드의 8번째 줄을 아래처럼 에러가 발생하도록 의도적으로 수정 후에 코드를 실행해보면, 다음과 같은 에러 메시지를 보게 된다.
.then(user => user1.name);
ReferenceError: user1 is not defined at <anonymous>:8:34
then()을 연쇄적으로 호출하고 있어 몇 번째 then()에서 문제가 발생한 건지 한번에 알기 어렵다.
또한 then() 메서드 호출부에 break point를 걸고 디버거를 돌리면, 위 코드와 같이 화살표 함수로 한 줄짜리 콜백함수를 넘긴 경우에는 코드 실행이 break point에서 멈추지 않기 때문에 디버깅이 상당히 불편하다.
2/ 예외 처리의 어려움
Promise를 사용하면 try/catch 대신에 catch()메서드를 사용하여 예외 처리를 해야한다.
비동기 코드만 있다면 괜찮지만, 동기 코드와 비동기 코드가 섞여있을 경우 예외 처리가 난해해지거나 예외 처리를 누락하는 경우가 생기기 쉽다.
3/ 들여쓰기
실제 프로젝트에서는 복잡한 구조의 비동기 처리 코드를 작성하게 된다. 따라서 then() 메서드의 인자로 넘기는 콜백함수 내에서 조건문이나 반복문을 사용하거나, 여러 개의 Promise를 병렬로 또는 중첩해서 호출해야하는 경우들이 발생하게 된다.
이럴 경우, 다단계 들여쓰기를 해야할 확률이 높아지며 코드 가독성은 점점 떨어지게 된다.
# async/await 키워드를 통한 비동기 코딩
Promise의 이러한 불편한 점들을 해결하기 위해 ES7에서 async/await 키워드가 추가되었다.
async/await 키워드를 사용하면, 비동기코드를 마치 동기 코드처럼 보이게 작성할 수 있다.
function fetchAuthorName(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((response) => response.json()) //resolve()함수의 인자로 넘겨받은 값이 response가 된다.
.then((post) => post.id) //위 return한 response.json()값이 post가 된다.
.then((userId) => { //위 return한 post.id가 콜백함수의 userId가 된다.
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((response) => response.json()) //위 fetch함수의 return값인 Promise객체의 resolve함수의 인자값
.then((user) => user.name); //위 response.json()값이 user이 된다.
});
}
fetchAuthorName(1).then((name) => console.log("name:", name)); //위 user.name값이 name이 된다.
▼
async function fetchAuthorName(postId) {
const postResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`); // Response객체
const post = await postResponse.json(); //postResponse.json()도 promise객체를 반환하는 비동기함수.
const userId = post.userId;
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
const user = await userResponse.json(); //userResponse.json()도 promise객체를 반환하는 비동기함수.
return user.name;
}
fetchAuthorName(1).then((name) => console.log("name:", name)); // name: Leanne Graham
달라진 점
- 함수 선언부에 async 키워드가 function 앞에 붙었다.
- Promise를 리턴하는 모든 비동기 함수 호출부 앞에는 await 키워드가 추가됨.
- await 키워드는 async 키워드가 붙어 있는 함수 내부에서만 사용할 수 있으며, 비동기 함수가 리턴하는 Promise로부터 결과값을 추출해준다.
즉, await 키워드를 사용하면 일반 비동기처리처럼 바로 실행이 다음 라인으로 넘어가는 것이 아니라, 결과값을 얻을 수 있을 때까지 기다려준다.
따라서 일반적인 동기 코드처리와 동일한 흐름으로(함수 호출 후 결과값을 변수에 할당하는 식) 코드를 작성할 수 있으며, 따라서 코드를 읽기도 한결 수월해진다.
위 코드에 추가설명을 하자면,
async function fetchAuthorName(postId) {
const postResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
const post = await postResponse.json(); //postResponse.json()도 promise객체를 반환하는 비동기함수.
- fetch(`https://....`)함수 : Promise객체를 반환, Response 객체를 resolve.
따라서, 위 코드에서 postResponse는 Response객체.
Response 객체 : JSON응답 본문을 그대로 포함하지는 않는다. HTTP 응답 전체를 나타내는 객체. Response 객체로부터 HTTP 응답상태(status), HTTP 응답 헤더(header), HTTP 응답 전문(body) 등이 있다.
여기서 JSON형태로 된 HTTP 응답 전문(body)를 추출하기 위해서는 json() 메서드를 호출해야한다.
json()은 응답 본문 텍스트를 파싱한 결과.
- await postResponse.json() : Response객체인 postResponse의 json()함수가 반환하는 Promise 객체 : 응답 body로 수신된 JSON문자열을 자바스크립트 값(객체, 문자, 문자열 등..)으로 변환한 결과를 resolve.
json()함수 또한, 비동기 함수이므로 앞에 await을 붙였다.
# 주의할 점
한 가지 주의할 점은, async 키워드가 붙어있는 함수를 호출하면 명시적으로 Promise 객체를 생성하여 리턴하지 않아도 Promise객체가 리턴된다.
async function fetchAuthorName(postId) {
const postResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`); //결과값 추출
const post = await postResponse.json();
const userId = post.userId;
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
const user = await userResponse.json();
return user.name;
}
fetchAuthorName(1) //Promise {<pending>}
따라서, Promise객체를 사용했던 것과 동일한 방식으로 then() 메서드를 통해서 결과값을 출력할 수 있다.
하지만 만약 이 호출부(fetchAuthorName(postId))가 또 다른 async 키워드가 붙어있는 함수의 내부에 있다면, 동일한 방식으로 await 키워드를 사용하여 Promise가 제공할 값(여기서는 name)에 바로 접근할 수 있다.
async function printAuthorName(postId) {
const name = await fetchAuthorName(postId);
console.log("name:", name);
}
printAuthorName(1); //Leanne Graham
# 예외 처리
동기/비동기 구분 없이 try/catch로 일관되게 예외 처리를 할 수 있는 것도 async/await를 사용해서 코딩했을 때의 큰 이점 중 하나이다.
async function fetchAuthorName() {
const postResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`); //결과값 추출
const post = await postResponse.json();
const userId = post.userId;
try {
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
const user = await userResponse.json();
return user.name;
} catch(err) {
console.log("Fail to fetch user:", err);
return "Unknown";
}
}
fetchAuthorName(1).then((name) => console.log("name:", name));
* 참고글 : https://www.daleseo.com/js-async-async-await/
'JavaScript' 카테고리의 다른 글
[JS] 배열 비우기(빈 배열로 만들기, 초기화) 방법 (3) | 2024.09.02 |
---|---|
자바스크립트 동작 원리(Call Stack, Task Queue, Event Loop etc) (0) | 2024.02.04 |
비동기처리 - Promise (1) | 2023.12.29 |
비동기함수의 순서를 제어하는 방법 1. Callback함수 (2) | 2023.12.23 |
callback함수란? (0) | 2023.12.23 |