Promise 다루는 방법은 크게 두가지 있다.
- async 함수와 await 문법
- then() 메소드 + Callback
먼저 async 함수와 await 문법을 다뤄보자
await 문법
fetch 함수는 Promise 객체를 리턴한다고 했을 때
비동기 작업이 완료되면 Promise가 결과값을 알려 준다고 했을 때
그 결과값을 받아오려면 await 문법을 사용한다.
// main.js
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
console.log(response);
이렇게 Promise를 리턴하는 부분 앞에 await 키워드를 작성하면 되는데 실행해보면
Promise가 바로 response에 할당되는 것이 아니라
fetch 작업이 완료될 때까지 기다렸다가
fetch의 결과값이 response에 할당된 것이다.
원래 response는 이렇게 복잡하게 생겼기 때문에
데이터를 얻기 위해서는 response를 parsing 해야하며
그러기 위해선 response의 json 메소드를 사용해야 한다.
// main.js
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = response.json()
console.log(data);
json 메소드로 response를 parsing해서 data 변수에 저장했다.
실행해보면 Promise pending이 출력되는걸 확인할 수 있는데
json 메소드도 실행하는 데 오랜 시간이 걸릴 수 있기 때문에 Promise를 리턴하는 것이다.
그래서 json 메소드도 await를 사용해보면 데이터가 잘 출력될 것이다.
참고로 await 부분을 하나의 표현식으로 생각해서
다음과 같이 작성해 줄 수 있다.
// main.js
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
// const data = await response.json()
// console.log(data);
console.log(await response.json());
Promise와 await 문법의 동작 원리
// main.js
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = await response.json()
console.log(data);
fetch 함수는 Promise를 리턴한다고 했을 때
Promise는 3가지 중 하나의 상태를 가진다.
- 비동기 작업의 결과를 기다리고 있을 때 - 팬딩(Pending)
- 비동기 작업이 성공적으로 완료됐을 때 - 풀필드(Fulfilled)
- 비동기 작업이 중간에 실패했을 때 - 리젝티드(Rejected)
처음에는 Promise가 Pending 즉, 미결정된 상태인데
그래서 아래와 같이 Promise를 바로 출력했을 때는
Promise panding 결과가 출력 되었던 것이다.
하지만 Promise 앞에 await를 넣어주면
Promise가 Fulfilled 또는 Rejected 상태가 될 때까지 기다리는데
일단 Promise가 Fulfilled가 되는 경우
비동기 작업의 성공 결과. fetch의 경우 response를 Promise의 결과값으로 갖게 된다.
그리고 await은 Promise의 결과값을 꺼내서 리턴해준다.
그래서 response가 response 변수에 할당되는 것이다.
두 번째 줄을 보면 response.json 역시 Promise를 리턴하는데
마찬가지로 처음에는 Promise가 Pending 상태이지만
앞에 await이 있기 때문에 parsing 작업이 끝날 때까지 기다린다.
parsing 작업이 끝나면 Promise는 Fulfilled 상태가 되고
성공 결과, 즉 parsing된 데이터를 결과값으로 갖게된다.
그리고 그 결과 값이 data에 할당되는 것이기에 data가 잘 출력된다.
그래서 가장 중요한 것은
Promise를 리턴하는 표현식이 있다면
그 앞에 await 키워드를 써서 결과값을 받아올 수 있다는 것이다.
await를 쓰면 Promise가 Fulfilled 상태가 될 때까지 기다렸다가 결과값을 돌려주기 때문이다.
이 사실만 기억하고 있으면 Promise를 사용하는 코드도 쉽게 작성할 수 있을 것이다.
async 함수
원래 비동기 프로그램의 목적은 비동기 작업의 결과를 기다리는 동안
다른 작업을 먼저 처리하는 것이라고 하였을 때,
// main.js
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = await response.json()
console.log(data);
console.log('Task 2');
console.log('Task 3');
만약 저 상태로 출력하면 data가 먼저 출력되고
그 뒤로 Task 2, 3이 출력될 것이다.
코드가 위에서 부터 순서대로 실행되고 있는 것인데
fetch 결과를 기다리는 동안 아래의 출력물을 먼저 처리해 주고자 할 때,
Promise와 await로 비동기 처리를 제대로 하려면 함수를 이용해야 한다.
일단 다음과 같은 새로운 파일을 만들어준다.
▶ asyncFunction.js 파일
// asyncFunction.js
export function printList () {
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = await response.json()
console.log(data);
}
printList라는 함수를 만들었고
main.js에있는 response와 data를 가져왔는데
await은 async 함수 내에서나 모듈(module)의 최상위 레벨에서만 사용할 수 있다고 오류가 발생한다.
여기서 모듈은 ES 모듈을 뜻하는데
package.json 파일에서 type을 module로 설정한 상태에서
지금 작업하는 디렉토리 안에서는
파일들이 기본적으로 ES 모듈로 인식되기 때문에 함수 바깥에서도 await을 쓸 수 있었다.
만약 type module을 지우게 되면 CommonJS 모듈로 인식하기 때문에 오류가 발생했을 것이다.
await으로 비동기 처리를 제대로 하려면 함수를 이용해야하기 때문에
함수 바깥에서 await을 쓰는 일이 흔치는 않지만
ES6 모듈 안에서만 가능하다는 사실을 기억해야한다.
다시 돌아와서, async 함수 안에서만 사용할 수 있다는 말은
function 앞에 async 키워드를 사용하라는 말로,
한번 async 키워드를 function 앞에 넣어보면 오류가 사라진 것을 확인할 수 있을 것이다.
// asyncFunction.js
export async function printList () {
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = await response.json()
console.log(data);
}
만약 일반함수가 아닌 Arrow Function 문법으로 async 함수를 정의하려면
다음과 같이 작성한다.
const printListArrow = async () => {
const response = await fetch('https://todo-api.fesp.shop/api/todolist');
const data = await response.json()
console.log(data);
}
이제 main 파일에서 printList 함수를 가져와서 호출하고 출력결과를 보면
// main.js
import { printList } from "./asyncFunction.js";
printList();
console.log('Task 2');
console.log('Task 3');
이번엔 Task 2랑 Task 3이 먼저 출력되고
그 아래 데이터가 출력된 것을 확인할 수 있다.
실행 순서는 다음과 같다.
▶ main.js 파일 내 printList 함수를 import하고printList 함수를 실행
- 함수 안으로 들어가서 fetch를 실행함 (asyncFunction.js)
- fetch는 Promise를 리턴하는데 await문이 있기 때문에 Promise가 Fulfilled 상태가 될 때까지 기다림
▶ "const response = await fetch(...)" => await을 사용
- Promise가 Fulfilled가 될 때까지 기다림
- 다시 함수 바깥으로 나가서 나머지 코드를 모두 실행 => console.log('Task 2'), ('Task 3')
그래서 아래에 있는 Task 2, Task 3이 실행된 것이며
Promise가 Fulfilled 상태가 되고 성공 결과를 갖게 되면
다시 함수의 body로 돌아와서 코드를 이어서 실행한다.
▶ "const data = await response.json()" => 다시 await 사용
- 다시 함수 바깥으로 나가서 코드를 실행
- 하지만 실행한 코드가 더 이상 없기 때문에 그냥 기다림
- Promise가 Fulfilled 상태가 되면 다시 돌아와서 코드를 마저 실행. (data 출력)
Promise를 이용한 비동기 프로그램을 짜려면
위 흐름을 이해하는 것이 정말 중요하다.
'JS' 카테고리의 다른 글
(8) Asynchronous JavaScript - async 함수의 리턴값 (0) | 2025.01.03 |
---|---|
(7) Asynchronous JavaScript - 효율적인 비동기 코드 (0) | 2025.01.03 |
(5) Asynchronous JavaScript - Promies (0) | 2025.01.01 |
(1) ~ (4) Callback 정리 (0) | 2025.01.01 |
(4) Asynchronous JavaScript - Callback Hell (0) | 2025.01.01 |