사용 할 API : Color Survey API MBTI 컬러 설문

//package.json
{
  type:"module"
}

import, export 문법과 모듈 최상위 레벨에서 await을 사용할 수 있도록 type을 module로 설정해 준다.

우선 fetch로 컬러 설문 목록을 가져와 보자.

fetch('https://learn.codeit.kr/api/color-surveys');

fetch 함수 안에 리퀘스트(웹 브라우저가 서버에 요청)를 보낼 URL을 쓰면 된다. fetch는 기본적으로 get 리퀘스트를 보내고 promise를 리턴한다. 시간이 지나고 리스폰스(서버가 보내주는 응답)가 도착하면 promise는 fulfilled 상태가 되고, 리스폰스를 결과값으로 갖게된다. 그래서 리스폰스를 가져오려면 await문을 써줘야 하는것이다.

const res = await fetch('https://learn.codeit.kr/api/color-surveys');

console.log(res); //리스폰스 출력

출력된 결과, 이 밑으로도 내용이 쭉 있다.

조금 복잡한 결과가 출력됐는데, 리스폰스에는 다양한 정보가 있다. 이중에서 가장 많이 사용하는 것은 상태코드, 헤더와 바디 내용이다.

const res = await fetch('https://learn.codeit.kr/api/color-surveys');
console.log('Status Code:');
console.log(res.status); //상태코드
console.log('Headers:');
console.log(res.headers); //헤더

상태코드 200과 헤더의 길고 긴 정보들. 이 중 content-type value: application/json을 기억하자!

이렇게 status와 headers로 프로퍼티로 접근할 수 있다. 반면 body는 내용이 일반 문자열이라면 텍스트 메소드, json 문자열이라면 json 메소드를 써야한다. 위의 헤더 내용 중 content-type value: application/json을 확인 할 수 있는데 사용할 API가 json 문자열을 돌려준다는 의미이기 때문에 json 메소드를 사용하면 된다.

//바디 내용은 단순히 프로퍼티로 가져올 수 없고 메소드를 사용해야 한다.
const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();
console.log(data);

json 메소드 역시 promise를 리턴하는 비동기 함수이기 때문에 앞에 await을 넣어줘야한다. json 메소드는 바디의 json 문자열을 읽어서 자바스크립트 객체로 변환해 준다.

리스폰스의 바디 내용이 잘 출력된다.

실제 설문 데이터 외에도 count나 next, previous 같은 필드들이 있는데, count는 모든 데이터 갯수, next는 다음 데이터를 받아오는 URL, previous는 이전 데이터를 받아오는 URL, 그리고 results는 현재 페이지에 해당하는 데이터 배열이다. (자세한건 다음 포스팅 참조)

데이터 변수에 할당된 이 결과는 자바스크립트 객체이기 때문에 디스트럭처링(Destructuring) 같은 문법을 사용해서 프로퍼티들을 쉽게 가져올 수 있다.

예를 들어 설문 목록에 해당하는 results 프로퍼티를 가져와서 첫번째 설문 객체의 프로퍼티들을 출력해본다면,

const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();

const { results } = data;
const survey = results[0];
const{ id, mbti, colorCode, createAt, updateAt } = survey;
console.log(id, mbti, colorCode, createAt, updateAt);

이렇게 프로퍼티들이 잘 출력된다. 


총 정리

const res = await fetch('URL');
//fetch: 아규먼트로 URL을 전달

// 리스폰스 상태 코드
res.status;

// 리스폰스 헤더
res.headers;

// 리스폰스 바디
await res.json(); // JSON 문자열을 파싱해서 자바스크립트 객체로 변환
await res.text(); // 문자열을 그대로 가져온다.

fetch와 json 함수 모두 promise를 리턴하기 때문에 앞에 await을 써줘야 한다.

실무에서는 여러개의 promise 객체를 다뤄야할 일도 종종 발생한다. 

데이터를 바로 출력하지 않고 배열에 저장해두었다가 모든 데이터가 저장되고 나서 배열을 출력해보도록 하자.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

for (let i = 1; i < 11; i++) {
  const employee = await getEmployee(i); //함수 바깥에서 값을 받아오려면 await문을 써야한다.
}

(for문) 함수 바깥에서 값을 받아오려면 await 문을 써야한다. 하지만 await이 있으면 getEmployee 함수가 완전히 끝날 때까지 기다린 후에 다음줄로 넘어가기 때문에 리퀘스트를 보내고 파싱하는 작업을 순차적으로 하게된다. 이럴 때 promise.all 메소드를 활용할 수 있다. 

Promise.all은 여러 promise를 동시에 기다릴때 사용한다.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};
//promises 배열에 추가되는 것은 promise 객체이다.
const promises = []; //일단 이렇게 promises라는 배열을 만들고

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i)); //getEmployee의 결과를 await하지않고 바로 배열에 추가
}

이렇게 하면 await을 하지 않기 때문에 리퀘스트를 거의 병렬적으로 보내게된다. (promises 배열에 추가되는 것은 promise 객체이다.)

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

const promises = []; 

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

Promise.all(promises); //추가

Promise.all(promises); 를 풀어서 작성해보자면 이렇게 된다.

Promise.all([p1,     p2,     p3,     p4,     p5,     ...]);

처음에는 배열에 있는 promise들이 모두 pending 상태일 것이다.

pending 상태

Promise.all 메소드도 promise를 리턴하는데 처음에는 pending 상태이다가 아규먼트로 전달된 promise들이 모두 fulfilled 상태가 되면 Promise.all도 fulfilled 상태가 되고, 

각 promise의 성공 결과값들로 이루어진 배열을 성공 결과값으로 갖게된다. 지금 쓰고 있는 예시의 경우 직원 데이터로 이루어진 배열이 성공 결과값이 될것이다.

반대로 아규먼트로 전달한 promise 중 하나라도 rejected 상태가 되면 Promise.all도 rejected 상태가 되고 rejected된 promise의 오류를 결과값으로 갖게된다.


Promise.all도 결국 promise 객체를 리턴하기 때문에 await이나 then 메소드를 이용할 수 있다.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

const promises = []; 

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

const employees = await Promise.all(promises); //await 이용
console.log(employees);

await을 이용해서 성공 결과값, 즉 직원 배열을 가져왔다. 위 코드를 실행해보면 직원 코드가 잘 출력된다.


Promise.all도 try catch문을 이용해서 오류를 처리할 수도 있다.

async function getEmployee(id) {
  const response = await fetch(`https://learn.codeit.kr/api/employees/${id}`);
  const data = await response.json();
  return data;
}

const promises = [];

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

let employees; //try문 바깥에서 선언

try {
  employees = await Promise.all(promises);
} catch (error) {
  console.log(error);
}

console.log(employees);

getEmployee 변수를 try문 바깥에서도 사용할 수 있도록 try문 바깥에서 선언했다. 그러면 Promise.all 부분에서 오류가 나도 try catch문이 오류를 잡아준다.

promise를 활용하는 비동기 작업 여러개를 병렬적으로 처리하고 싶을 땐 Promise.all 메소드를 사용하면 된다. (여러 비동기 작업을 한꺼번에 처리)

then 메소드와 프로미스 체인을 사용할 때는 catch와 finally 메소드를 사용할 수 있다.

fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log('Error!')) //코드 추가
  .finally(() => console.log('Finished')); //코드 추가
  
console.log('Task 2');
  
console.log('Task 3');

//위 코드는 아래 코드와 동일하다.
//export async function printEmployees() {
//  try {
//    const response = await fetch('https://learn.codeit.kr/api/employees');
//    const data = await response.json();
//    console.log(data); 
//  } catch (error) {
//    console.log('Error!');
//  } finally {
//    console.log('Finished');
//  }
//};

//printEmployees();

//console.log('Taks 2');
//console.log('Taks 3');

catch 메소드는 프로미스 체인에서 발생하는 오류를 잡아서 콜백을 실행해주고, finally 메소드는 프로미스 체인에서 어떤 일이 발생하든 끝에 콜백을 실행해준다. 코드를 실행해보면

Task 2
Task 3
[  
  {
    id: 1,
    name: 'Alice',
    email: 'alice@codeitmall.kr',
    department: 'engineering'
  },
...
]
Finished

Task 2와 3이 먼저 출력되고, 직원 데이터가 출력된 다음에 Finished 가 출력된 후 종료된다.

만약 주소가 잘못된 주소라면?

fetch('https://learn.codeit.krrrrrrrrrrrrrr/api/employees') //잘못된 주소 입력
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log('Error!')) 
  .finally(() => console.log('Finished')); 
  
console.log('Task 2');
  
console.log('Task 3');
Task 2
Task 3
Error!
Finished

출력되는 순서는 이전과 똑같은데 이전에 출력됐던 직원 데이터 대신 Error!가 출력된다.


1. fetch 함수와 then, catch, finally 메소드는 모두 promise를 리턴한다.

2. 그리고 fetch 함수가 실행되는 동안 프로미스 체인 아래에 있는 코드를 실행한다.

3. fetch를 할 때 잘못된 주소를 적었기 때문에 오류가 발생할 것이다. 그래서 첫번째 promise는 rejected 상태가 되고, 결과값으로 오류를 갖게 된다.

4. then 메소드는 앞선 promise가 fulfilled 상태가 되야 등록된 콜백을 실행하는데, rejected 상태이기 때문에 아무 콜백도 실행하지 않는다. (뒤에 있는 promise는 이전 promise와 똑같은 상태와 결과값을 갖게된다.)

그러면 다음 then에 연결되어 있는 promise도 당연히 rejected가 된다.

5. catch 메소드는 이전 promise 상태가 rejected 상태가 되면 promise의 결과값을 가지고 콜백을 실행하는데 then의 반대라고 생각하면 된다. 
6. catch문의 콜백을 실행하고(Error! 출력) 

6-1 콜백이 promise를 리턴하면 catch 메소드가 리턴하는 promise도 동일한 상태와 결과값을 갖게 되고
6-2 콜백이 일반값을 리턴하면 catch 메소드가 리턴하는 promise는 fulfilled 상태가 되고 리턴 값을 결과값으로 갖게된다. (지금은 console.log를 리턴하고 있는데 console.log는 undefined를 리턴한다.)
7. promise가 fulfilled상태가 되고 undefined를 결과값으로 갖게 된다.

catch에서 오류를 이미 처리했기 때문에 콜백이 잘 실행되면 catch가 리턴하는 promise는 rejected가 아닌 fulfilled 상태를 갖는다.

8. 마지막으로 finally 메소드는 앞선 promise의 결과가 결정(fullfilled 혹은 rejected 상태)되면 등록된 콜백을 실행한다. 그래서 오류가 발생하든 안하든 항상 finally 콜백은 실행된다. 

fianlly 메소드의 등록된 콜백 실행

9. 그리고 이번에도 finally가 리턴한 promise는 fulfilled 상태가 되고, undefined를 결과값으로 갖게된다.

‼️결론

  • promise 객체의 then, catch, finally 메소드를 연결해서 프로미스 체인을 만들 수 있다. 
  • then 안에는 앞에 작업이 성공했을 때 실행할 콜백을 등록하면 된다.
  • catch 안에는 앞서는 프로미스 체인 어딘가에서 오류가 났을 때 실행할 콜백을 등록하면 된다.
  • 오류가 어디서 나든 rejected 상태의 promise가 계속 전파되는 원리 때문에 catch 메소드가 오류를 처리할 수 있다.
  • 그래서 catch 메소드는 보통 then 이후에 사용한다.
  • finally는 앞 선 promise의 결과가 어떻든 해결이 되면(더 이상 pending 상태가 아니면) 안에 있는 콜백을 실행한다. 
  • 프로미스 체인의 결과와 상관 없이 항상 실행되어야 하는 코드가 있다면 finally에 등록한다.
  • 프로미스 체인 어디에서든 결과를 기다리는 도중에는 이후에 있는 코드를 실행하고, 결과가 나오면 다시 체인으로 돌아오기 때문에 코드를 비동기적으로 처리할 수 있다.

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

리퀘스트: fetch 기본 문법  (0) 2024.12.25
Promise.all()  (1) 2024.12.25
.then() 메소드  (0) 2024.12.25
promise rejected 상태 이해하기  (0) 2024.12.25
오류 처리하기 (try catch)  (1) 2024.12.23

자바스크립트에서 비동기 코드를 잘 활용하려면 async, await과 then 메소드 둘 다 알고 있는 것이 좋다. 

//main.js
async function printEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
}

오류처리는 하지 않는 printEmployees 함수를 then 문법으로 바꿔본다면,

const dataPromise = fetch('https://learn.codeit.kr/api/employees')
					  .then((response) => response.json());
dataPromise.then((data) => console.log(data));

이렇게 바꿀 수 있다. 살펴보자면,

1. fetch는 promise를 리턴한다. (1번째 줄)
 2. then은 promise 객체의 메소드이기 때문에 뒤에 이어서 쓸 수 있다.(2번째 줄)
3. 그리고 then메소드 자체도 promise를 리턴한다. (console.log(dataPromise)를 입력해보고 실행해보면 promise pending 상태가 나온다.)
4. 그래서 dataPromise 뒤에서 then 메소드를 붙일 수 있다. (3번째 줄)

then 메소드는 앞 선 비도기 작업이 완료되면 등록된 콜백을 실행해준다. 

1. 리스폰스가 돌아오면 (response) => response.json() 콜백을 실행하고
2. response.json이 완료되면 (data) => console.log(data) 콜백을 실행한다.

이렇게 해서 실행해봐도 직원 정보가 잘 출력되는 결과를 확인 할 수 있다. 위 코드를 조금 더 간결하게 쓸 수 도 있는데,

fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data));

이렇게 작성할 수도 있다. then은 promise를 리턴하기 때문에 then 뒤에 바로 then을 또 붙일 수 있는 것이다. 

이렇게 promise 뒤에 메소드를 계속 연결해서 쓰는것을 프로미스 체인(Promise Chain)이라고 한다.

작동방식을 세세하게 살펴보자.

1. fetch 함수는 URL로 리퀘스트를 보내고 곧바로 promise를 리턴한다.
2. 그리고 이어서 then 메소드 들도 promise를 리턴한다.

3. 이 때 안에 있는 콜백을 실행하는건 아니고 일단 pending 상태의 promise를 리턴하는 것이다.
4. 기다리다가 리퀘스트가 완료되서 리스폰스가 돌아오면 첫번째 promise는 fulfilled 상태가 된다.

5. promise가 fulfilled 상태가 되면 then 메소드에 등록된 콜백이 실행된다.
6. 그리고 이 때 promise의 결과값을 콜백의 첫번째 파라미터로 전달한다. (그래서 리스폰스가 콜백으로 전달)

리스폰스가 콜백으로 전달

6-1. 만약 콜백이 promise를 리턴하면 then 메소드가 리턴한 promise도 동일한 상태와 결과값을 갖게 된다.

6-1예시

6-2. 콜백이 promise가 아닌 평범한 값을 리턴하면 then 메소드가 리턴한 promise는 fulfilled 상태가 되고, 리턴값을 결과값으로            갖게 된다.

6-2 예시

7. 지금의 경우에는 response.json을 리턴하니까 response.json이 fulfilled 상태가 되면 
8. then 메소드가 리턴한 promise도 fulfilled 상태가 된다.

9. fulfilled 상태가 되면 다음 then 메소드에 등록된 콜백이 실행되는데 파싱된 데이터가 첫번째 파라미터로 전달된다.

파싱된 데이터가 파라미터로 전달!

10. 이 콜백은 데이터를 출력하고 console.log를 리턴하는데 console.log는 undefined를 리턴하기 때문에 결국 콜백은 undefined를 리턴한다.

11. 그래서 마지막 promise는 fulfilled 상태가 되고, undefined 값을 결과로 갖게 된다.

결과

이 promise에 무언가를 이어서 하지 않을거기 때문에 마지막 promise에 대해서는 신경쓰지 않아도 된다.


프로미스 체인(Promise Chain)의 중요한점

  • 비동기 작업을 기다리는 동안에는 다른 코드를 먼저 실행한다.
fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data));
  
console.log('Task 2'); //코드 추가
  
console.log('Task 3'); //코드 추가

예를 들어 이렇게 코드를 추가해 보면 결과는 Task 2와 3이 먼저 출력되고 직원 데이터가 그 뒤를 이어 출력된다. 

실행 순서를 되짚어보자면 fetch 함수가 promise를 리턴하고, 이 promise가 pending 상태인 동안 console.log('Task 2')와 console.log('Task 3')을 실행한다. 그리고 조금 기다린 후 fulfilled 상태가 되면 리스폰스를 가지고 첫번째 then 메소드의 콜백을 실행한다. 그러면 이 콜백은 다시 promise를 리턴하고, 이 promise가 fulfilled가 될때까지 나머지 코드를 먼저 실행하는데 이번엔 더 이상 실행할 코드가 없기 때문에 잠시 기다렸다가 fulfilled 상태가 되면 promise의 결과값, 파싱 결과로 두번째 then 메소드의 콜백을 실행한다. 그러면 이제 직원 데이터가 실행되고 프로그램이 종료된다.

promise는 세가지의 상태를 가질 수 있다. (반복학습!)

pending : 기다리고 있는 상태 / fulfilled : 작업이 성공적으로 완료된 상태 / rejected : 작업이 실패한 상태

export async function printEmployees() {
  try {
    const response = await fetch('https://learn.codeit.krㄱㄱㄱㄱ/api/employees');
    const data = await response.json();
    console.log(data); 
  } catch (error) {
    console.log('Error!');
    return; 
  } finally {
    console.log('Finished');
  }
};

비동기 코드를 실행하는 동안 오류가 나면 promise는 rejected 상태가 되고, 발생한 오류를 결과값으로 갖게 된다. 그리고 promise 앞에 await이 있을 때는 promise의 결과값인 오류를 throw 해준다. try문 안에서 오류가 throw(던지다라는 의미)됐기 때문에 오류를 잡아서 처리했던 것이다.

코드 흐름 살펴보기

  1. 메인 파일을 실행한다고 했을 때 일단 printEmployees 함수를 import하고 printEmployees 함수를 실행(main 파일의 3번째줄)해서
  2. 안에 있는 fetch를 실행(asyncFunction 파일의 try문 안에 fetch)한다.
  3. fetch는 promise를 리턴하는데 앞에 await이 있기 때문에 결과를 기다리는 동안 함수 밖으로 나가서(main 파일로 이동) 이후 작업(main 파일 5, 7번째 줄)을 실행한다.
  4. 그럼 이제 비동기 작업은 성공하거나 실패를 할텐데 성공하면 promise 상태는 fulfilled 상태로 바뀌고 비동기 작업의 성공 결과값을 갖게 된다. 실패를 하면 promise는 rejected상태로 바뀌고 비동기 작업에서 발생한 오류를 결과값으로 갖게 된다. 어쨌든 promise가 pending에서 fulfilled나 rejected 상태가 되면 다시 함수 안으로 돌아와서 코드를 실행(main 파일에서 asyncFunction 파일 3번째줄 로 다시 이동한 후 코드를 실행)한다.
  5. fulfilled 상태면 promise의 값을 꺼내서 리턴해주고, 지금처럼 rejected 상태면 promise의 값을 throw 한다. 이 코드는 try 문으로 감싸져 있기 때문에 안에서 오류를 throw해도 잡아서 처리를 해준다.
  6. catch문 안에서 리턴을 해도 finally에 이는 코드를 실행하고 함수가 종료된다.
Task 2
Task 3
Error!
Finished

promise와 await의 원리 덕분에 비동기 코드를 익숙한 동기 형태로 작성할 수 있다. 비동기 작업의 결과값을 변수에 할당해서 다음 줄에서 활용하고 오류가 발생할 것을 대비해 try catch문으로 감싸주면 익숙하게 비동기 코드를 작성 할 수 있다. (단, 비동기 코드가 실행되는 순서를 조금 신경 써주어야 한다.)

fetch는

  1. 네트워크가 불안정하거나
  2. 서버 측에서 오류가 나거나
  3. 잘못된 URL에 리퀘스트를 보내거나 

하는 등의 다양한 이유로 리퀘스트가 실패할 수 있다. 이런 상황을 대비해서 적절히 오류 처리를 해주는 것이 좋다.

promise를 활용하는 비동기 작업 중 오류가 났을 때 어떻게 처리해야 할까?

//asyncFunctions.js
export async function printEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
  console.log('Finished');
};
//main.js
import { printEmployees } from './asyncFunctions.js';

printEmployees();

코드를 실행해보면 직원 데이터와 마지막에 Finished라고 출력된다. 프로그램이 정상적으로 실행되면 code = 0 으로 종료된다.

오류를 내기 위해서 일부러 잘못된 URL 주소를 입력해보고 오류처리를 해보자.

//asyncFunctions.js
export async function printEmployees() {
  const response = await fetch('https://learn.codeit.krㄱㄱㄱㄱ/api/employees');
  const data = await response.json();
  console.log(data);
  console.log('Finished');
};

이번엔 오류가 나고 finished가 출력되지 않았다. 그리고 code = 1로 종료된 것을 확인할 수 있다. promise 기반 코드에서 오류가 나는걸 처리하려면 trycatch문을 사용하면 된다.

try catch문은 어떤 오류가 발생했을 때 그걸 catch 해서 프로그램이 계속 실핼될 수 있게 해주는 문법이다. (이전 포스팅에서도 사용한 적 있다.)

//asyncFunctions.js
export async function printEmployees() {
  try {
    const response = await fetch('https://learn.codeit.krㄱㄱㄱㄱ/api/employees');
    const data = await response.json();
    console.log(data); 
  } catch (error) {
    console.log('Error!');
  }
  console.log('Finished');
};

코드의 흐름에 대해서 살펴보면 일단 try 블록 안에 있는 코드를 한줄 씩 실행한다. 오류가 나지 않으면 마지막 줄까지 실행하고 try catch문 바깥으로 나와서 console.log('Finished')를 실행한다. 하지만 만약 try 블록 안에서 오류가 나면 오류가 나는 시점에 바로 catch문 안으로 이동해서 'Error!' 라는 문자열을 출력하고 try catch 문 밖으로 나와 console.log('Finished')를 실행한다.

그래서 실행해보면 에러도 출력되고 Finished도 출력된다. 그리고 code = 0 으로 종료됐다.

catch 문 안에서는 에러 객체를 이용해서 더 세밀한 오류 처리를 할 수도 있는데 catch문 안에 console.log('Error!')을 console.log(error)로 바꿔 발생한 오류에 따라 다른 객체가 전달되도록 할 수도 있다.

출력 결과가 조금 복잡하긴 하다.


try catch는 finally 문도 같이 사용하는 경우가 많은데 finally문은 try catch결과가 어떻든 항상 끝에 실행된다. 

//asyncFunctions.js
export async function printEmployees() {
  try {
    const response = await fetch('https://learn.codeit.krㄱㄱㄱㄱ/api/employees');
    const data = await response.json();
    console.log(data); 
  } catch (error) {
    console.log('Error!');
    return; //여기에 return을 작성해주었다.
  }
  console.log('Finished');
};

catch문 안에 return을 쓰면 오류가 발생했을 경우 아래에 console.log('Finished')는 실행되지 않을 것이다. 

실행해보면 Error만 출력된다. 하지만 finally 블록을 만들고 console.log('Finished')를 그 안으로 옮겨주면 try나 catch문에서 어떤 코드가 실행되던 finally 안의 코드가 실행된다.

//asyncFunctions.js
export async function printEmployees() {
  try {
    const response = await fetch('https://learn.codeit.krㄱㄱㄱㄱ/api/employees');
    const data = await response.json();
    console.log(data); 
  } catch (error) {
    console.log('Error!');
    return; //여기에 return을 작성해주었다.
  } finally {
    console.log('Finished');
  }
};

실행해보면 Finished까지 출력된다. 

//asyncFunctions.js
export async function printEmployees() {
  try {
    const response = await fetch('https://learn.codeit.kr/api/employees');
    const data = await response.json();
    console.log(data); 
  } catch (error) {
    console.log('Error!');
    return; //여기에 return을 작성해주었다.
  } finally {
    console.log('Finished');
  }
};

그리고 물론 이렇게 try문 안에 있는 코드가 정상적으로 실행되도 finally 안에 있는 코드는 실행된다.

try catch문과 상관없이 끝에 무조건 실행해야 하는 코드가 있다면 finally를 활용하면 된다!

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

.then() 메소드  (0) 2024.12.25
promise rejected 상태 이해하기  (0) 2024.12.25
async 함수의 리턴 값  (0) 2024.12.23
async 함수 사용시 주의사항  (0) 2024.12.23
async 함수  (0) 2024.12.23

async 함수 안에서 await을 마주치면 뒤에 있는 promise가 fulfilled가 될 때까지 함수 바깥에 있는 코드를 실행하다가 다시 함수 안으로 돌아와서 코드를 이어서 실행한다. 

async에 대한 중요한 사실은 하나가 더 있다. async 함수는 항상 Promise를 리턴한다는 점이다.

//asyncFunctions.js에서 직원데이터를 출력하지 않고 리턴하도록 해보자. 
export async function getEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  return data;
};
//main.js에서 getEmployees 함수를 사용해보자.
import { getEmployees } from './asyncFunctions.js';

const employees = getEmployees(); //async는 항상 promise를 리턴하기 때문
console.log(employees);

getEmployees 함수의 리턴 값을 employees 변수에 할당하고 employees 변수를 출력한다. 코드를 실행해보면?

분명히 getEmployees 함수 안에서는 await을 잘 활용해서 직원 데이터를 추출해 냈음에도 불구하고 이 데이터를 employees 변수에 할당해서 출력하니까 promise pending이라고 나온다. 그 이유는 async 함수는 항상 promise를 리턴하기 때문이다.

async 함수가 promise를 리턴해야 하는 이유는 무엇일까?

함수가 async라는 것은 보통 안에 await문이 있다는 것이고, await 문이 있다는것은 어떤 비동기 작업, 비교적 오래 걸리는 작업을 처리한다는 것이다. 그러니 async 함수 자체도 끝까지 실행하려면 꽤나 오랜 시간이 걸린다.

getEmployees 함수도 끝까지 실행하려면 fetch와 json 함수를 기다려야한다. 그렇기 때문에 getEmployees 함수를 호출하면 리턴값을 바로 알 수가 없다. 그래서 async 함수는 일단 promise를 리턴해 주고 함수의 body가 다 실행되고 리턴값이 정해지면 promise를 fulfilled 상태로 바꿔주고 함수의 리턴 값을 promise의 결과값으로 채워준다.

//main.js에서 getEmployees 함수를 사용해보자.
import { getEmployees } from './asyncFunctions.js';

const employees = await getEmployees(); //await 추가
console.log(employees);

그니까 promise의 결과값을 받아오려면 앞에 꼭 await을 써줘야 한다! 조금 복잡하게 느껴지지만 promise에서 성공 결과값을 받아오려면 await을 써야 한다는 점을 잘 기억하도록 하자.

  • 함수 안에서 Promise를 리턴하면 그 Promise를 그대로 리턴
  • 함수 안에서 평범한 값을 리턴하면 그 값을 결괏값으로 갖는 Promise를 리턴

따라서 async 함수에서 리턴하는 값을 가져오려면 await을 활용!

더보기

한번 더 정리!

1. const employees = await getEmployees();로 getEmployees함수를 실행하면 pending 상태의 promise가 바로 리턴된다.

2.  getEmployees 함수(asyncFunctions.js)를 끝까지 실행하는데 시간이 조금 걸리는데, 끝까지 실행이 되고 data를 리턴하면

3. data가 ( const employees = await getEmployees();  //promise ) promise에 채워지고 promise의 상태는 fulfilled로 바뀐다.

4. getEmployees() 앞에 await을 써줬기 때문에 promise의 결과값이 employees 에 할당된다.

코드를 실행해보면 직원 데이터가 잘 출력된것을 확인 할 수 있다.


여담

이제 직원 데이터를 마음대로 활용할 수 있다.

//main.js
import { getEmployees } from './asyncFunctions.js';

const employees = await getEmployees(); //promise
const employee = employees[0]
console.log(employee);

위 코드의 결과

이렇게 배열에서 요소 하나만 가져올 수도 있다. 하지만 여기서 주의할 점이 하나 있다. async 함수 본문에서 promise를 리턴하는 경우인데, 

//asyncFunctions.js
export async function getEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  return response.json();
};

예를 들어 코드를 위처럼 바꾸면 response.json은 promise이다. 이런 경우엔 getEmployees가 리턴하는 promise(main.js의 4번째 줄)는 함수가 리턴하는 promise(asyncFunctions.js의 4번째 줄)와 동일한 상태와 결과값을 갖게 된다.

그러니까 response.json이 성공해서 해당하는 promise가 fulfilled 상태가 되고, 파싱된 데이터를 결과값으로 갖게 되면 getEmployees가 리턴하는 promise도 filfilled 상태가 되고 파싱된 데이터를 결과값으로 갖게 된다. getEmployees 앞에는 이미 await이 있기 때문에 코드를 실행해봐도 결과가 잘 출력된다. 

하지만 이 코드는 response.json이 비동기 작업이라는걸 알기 힘들기 때문에 이전에 작성했던 코드를 더 권장한다.

//asyncFunctions.js
export async function getEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data =  await response.json();
  return data;
};

이렇게 써있으면 어떤 작업을 await하는지 한눈에 알아보기가 쉽고 함수의 흐름에 대해 생각하기도 더 편하다.

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

promise rejected 상태 이해하기  (0) 2024.12.25
오류 처리하기 (try catch)  (1) 2024.12.23
async 함수 사용시 주의사항  (0) 2024.12.23
async 함수  (0) 2024.12.23
await 문법  (0) 2024.12.22
'https://learn.codeit.kr/api/employees'

이 주소로 들어가보면 데이터들의 배열을 볼 수 있었다.

주소 뒤에 /1 이렇게 ID를 붙이면 특정 직원에 대한 정보를 가져올 수 있는데, 1-10번에 해당하는 직원 정보를 각자 가져와서 출력해보자.

URL 주소 뒤에 /1 을 붙이면 1번 직원의 정보만 볼 수 있다.

//for문을 사용하는 printEmployees 함수를 정의해보자.
async function printEmployees() {
  for (let i = 1; i < 11; i++) {
    const response = await fetch(`https://learn.codeit.kr/api/employees/{i}`);
    const data = await response.json();
    console.log(data);
  }
};

printEmployees();

템플릿 문자열 (${i}) 을 이용해서 리퀘스트를 내보내고 있다. 이걸 실행해보면 직원 정보가 하나씩 잘 출력된다.

1번 직원부터 10번 직원까지 하나씩 잘 출력됐다. 총 2.286초 소요

하지만 이 코드는 사실 비효율적인 코드이다. 

작성한 for문(좌)과 for문을 풀어서 쓴 형태(우)

이 코드는 수많은 await문을 순차적으로 실행한다. 첫번 째 await문을 기다리는 동안 함수 바깥으로 나갔다가 filfilled가 되면 다시 돌아와서 두번째 await을 기다리고 함수 바깥으로 나갔다가 filfilled가 되면 다시 돌아오고… …  …  …  

like 무한굴레

이런식으로 비동기 작업을 하나씩 순서대로 기다리기 때문에 오래 걸리는 것이다. 비동기 작업을 하나하나 기다리지 않고 한꺼번에 리퀘스트를 보내고 결과를 기다리는 효율적인 방법이 없을까? 그러려면 코드를 변경해주어야 한다.

//파라미터로 id를 받게 하고 
async function printEmployee(id) {
  const response = await fetch(`https://learn.codeit.kr/api/employees/{id}`); //id를 넣어준 다음
  const data = await response.json();
  console.log(data);
};

//for문을 함수 바깥으로 옮겨준다.
for (let i = 1; i < 11; i++) {
  printEmployee(i);
};
//그리고 함수 이름도 printEmployees에서 printEmployee로 바꿔준다.

언뜻보면 이전과 비슷한 코드처럼 보이지만 이번에는 async 함수를 10번 호출하고 있다. 이 방법이 훨씬 효율적이다.

결과가 거의 바로 나왔는데 위에서 소요됐던 2.286초 보다 빠른 0.491초 걸렸다.

시간이 훨씬 빨랐지만 한가지 주의해야 될 점은 순서는 뒤죽박죽이라는 점이다. 왜 이 방법이 더 빠른걸까? 

이번 for문을 풀어서 쓰면 이런 모습이다.

  1. 가장 먼저 printEmployee (1) 을 실행한다. 
  2. 함수 안에서 fetch 리퀘스트를 보내고 기다리는 동안 바깥으로 나온다.
  3. 그 아래에 있는 코드는 printEmployee (2)이다. 그래서 fetch printEmployee/2의 리퀘스트를 보내고 다시 밖으로 나온다.
  4. 이런식으로 printEmployee 3, 4, 5, 6, 7의 리퀘스트를 보낸다.
  5. 중요! 리퀘스트의 결과를 기다리지 않고 바로 다음 URL에 리퀘스트를 보낸다는 것이다. (10개의 리퀘스트를 거의 동시에 보내는 것)
  6. 리퀘스트를 거의 동시에 보냈기 때문에 리스폰스도 거의 동시에 돌아오고 결국 하나의 리퀘스트를 처리하는데 걸린 시간만큼만 기다리면 된다.
  7. 이 때 리퀘스트를 보낸 순서대로 리스폰스가 돌아온다는 보장이 없어서 출력 순서가 조금 뒤바뀌는 것이다.

비동기 작업을 처리하는 순서는 중요하지 않고 작업들을 최대한 빨리 끝내는게 목표라면 이 방법을 사용하는게 좋다.

리스폰스가 돌아온 다음에는 리스폰스를 파싱하고, 파싱 작업이 끝나는대로 데이터를 출력한다.

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

오류 처리하기 (try catch)  (1) 2024.12.23
async 함수의 리턴 값  (0) 2024.12.23
async 함수  (0) 2024.12.23
await 문법  (0) 2024.12.22
Promise  (0) 2024.12.22
//main.js
const response = await fetch('https://learn.codeit.kr/api/employees');
const data = await response.json();
console.log(data);

console.log('Task 2');

console.log('Task 3');

원래 비동기 프로그램의 목적은 비동기 작업의 결과를 기다리는 동안 다른 작업을 먼저 처리하는 것이다. fetch 결과를 기다리는 동안 아래에 있는 코드(작업)들을 먼저 처리해주면 좋을 것 같은데, 코드를 실행해보면 직원 데이터가 먼저 출력되고, Task 2, Task 3이 출력된다. 코드가 위에서부터 아래로 순서대로 실행되고 있는 것인데 Promise와 await으로 비동기 처리를 제대로 하려면 함수를 이용해야한다.

//asyncFunctions.js
export function printEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
};

새로운 js 파일을 만들어 printEmployees라는 함수를 정의하고 main.js의 일부분을 긁어온다. 그런데 await에 빨간줄이 가있는걸 볼 수 있다.

await은 async 함수 내에서나 모듈(module)의 최상위 레벨에서만 사용할 수 있다고 한다. 일단 여기서 모듈이라는건 ES 모듈을 뜻하는데 package.json 파일에서 type을 모듈로 설정해 줬다. 

그래서 이 async-js 디렉토리 안에서는 파일들이 기본적으로 ES 모듈로 인식되기 때문에 함수 바깥에서도 await을 쓸 수 있었던 것이다. 만약 json 파일에서 type 모듈을 지우면 main.js 파일을 CommonJS 모듈로 인식하기 때문에 오류가 난다.
await으로 비동기 처리를 제대로 하려면 함수를 이용해야 하기 때문에 함수 바깥에서 await을 쓰는게 흔하진 않다. ES6 모듈 안에서만 이게 가능하다는 사실을 기억해두자.
다시 본론으로 돌아가서 async 함수 안에서만 사용할 수 있다는건 무슨 뜻일까? async 함수를 만들려면 function 앞에 async 키워드를 붙여주면 된다. 

//asyncFunctions.js
export async function printEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
};

//참고, 화살표 함수 문법으로 async 함수를 정의하려면? async의 위치가 조금 다르니까 주의!

const printEmployeesArrow = async () => {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
}

그러면 아까 오류를 보이던 await의 빨간 줄이 더이상 보이지 않는다. 그럼 main.js 파일에서 printEmployees 함수를 가져와서 호출해 보자.

//main.js
import { printEmployees } from './asyncFunctions.js'
printEmployees();

console.log('Task 2');

console.log('Task 3');

실행해보면 이번엔 아까와 다르게 Task 2와 Task 3이 출력되고 그 아래에 직원 데이터가 출력됐다. 
코드의 실행 순서를 살펴보면

  1. 가장 먼저 printEmployees 함수를 import 하고 (main.js 1번째 줄)
  2. printEmployees 함수를 실행한다. (main.js 3번째 줄)
  3. 그러면 함수 안으로 가서 fetch를 실행 (asyncFunctions.js 2번째 줄)
  4. fetch는 prmise를 리턴하는데 앞에 await 문이 있기 때문에 promise가 fulfilled 상태가 될 때까지 기다린다. (asyncFunctions.js 2번째 줄)
  5. 여기서 중요한 사실!  async 안에서 await 함수를 쓰면 promise가 fulfilled가 될 때까지 기다리는 동안은 다시 함수 바깥으로 나와서 나머지 코드를 모두 실행한다.
  6. 나머지 코드 = main.js의 5, 7번째 줄  (그래서 printEmployee 함수 아래에 있는 코드들이 먼저 실행된 것)
  7. 그러다 promise 상태가 fulfilled 가 되어 성공 결과를 갖게 되면
  8. 다시 함수의 body로 돌아와 코드를 이어서 실행한다. (asyncFunctions.js 2-3번째 줄)
  9. 그런데 여기에 또 promise를 await 하는 부분이 있다. (asyncFunctions.js 3번째 줄)
  10. 그럼 다시 함수 바깥으로 나가서 코드를 실행한다. (main.js로 가지만 남은 코드는 없다.)
  11. 이번엔 실행할 코드가 더이상 없기 때문에 그냥 기다리다가 promise가 fulfilled 상태가 되면 다시 돌아와서 코드를 마저 실행한다. (asyncFunctions.js 3-4번째 줄)
  12. 콘솔창에 직원 데이터 출력 (asyncFunctions.js 4번째 줄)
그래서 이런 결과창을 갖게 되는 것이다.

 

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

async 함수의 리턴 값  (0) 2024.12.23
async 함수 사용시 주의사항  (0) 2024.12.23
await 문법  (0) 2024.12.22
Promise  (0) 2024.12.22
콜백의 한계점(Callback Hell)  (0) 2024.12.22

fetch 함수는 Promise 객체를 리턴한다. 비동기 작업이 완료되면 promise가 결과값을 알려주는데 그 결과값을 받아오려면 await 문법을 쓰면 된다.

const response = await fetch('https://learn.codeit.kr/api/employees');

console.log(response);

이렇게 promise를 리턴하는 부분 앞에 await 키워드를 쓰면 되는데, 실행해보면 뭔가 복잡해보이는 것들이 출력된다. 이번에는 promise가 바로 리스폰스에 할당되는 것이 아니라 fetch 작업이 완료될 때까지 기다렸다가 fetch의 결과값이 리스폰스에 할당된 것이다.

원래 리스폰스는 복잡하게 생겨서 직원데이터를 얻기 위해서는 리스폰스(서버가 보내주는 응답)를 파싱(어떤 큰 자료에서 원하는 정보만 가공하고 뽑아서 원할 때 불러올 수 있게 하는것) 해야 한다. 그러기 위해선 리스폰스의 json 메소드를 쓰면 된다.

const response = await fetch('https://learn.codeit.kr/api/employees');
const data = response.json();
console.log(response);

json 메소드로 리스폰스를 파싱해서 데이터 변수에 저장해줬다.

실행 해본 결과

실행해보면 또 promise 라고 나오는데 json 메소드도 실행하는데 오랜 시간이 걸릴 수 있기 때문에 promise를 리턴하는 것이다. 그래서 여기 앞에도 await을 붙이면 직원 데이터가 잘 출력된다.

const response = await fetch('https://learn.codeit.kr/api/employees');
const data = await response.json();
console.log(response);

결과

참고로 await 부분을 하나의 표현식으로 생각해서 

const response = await fetch('https://learn.codeit.kr/api/employees');
console.log(await response.json());

이렇게 작성하는것도 가능하다. 일단 console.log( 이 부분 )을 실행하고 이거(await response.json())의 결과값을 출력하는 것이다. (하지만 이전 버전이 더 이해하기는 쉽다.)


promise와 await 문법의 동작 원리

fetch 함수는 promise를 리턴하고, promise는 3가지 중 하나의 상태를 갖는다.

  • Pending: 비동기 작업의 결과를 기다릴 때
  • Fulfilled: 비동기 작업이 성공적으로 완료됐을 때. 비동기 작업의 성공 결과를 결괏값으로 갖게 됨.
  • Rejected: 비동기 작업이 중간에 실패했을 때. 비동기 작업에서 발생한 오류를 결괏값으로 갖게 됨.

처음에는 Promise가 pending, 즉 미결정 된 상태이다. 그래서 이전처럼 promise를 바로 출력했을 땐 promise {<pending>}이라는 결과가 출력됐었다.


const result = await Promise;

// 예시
const response = await fetch('https://learn.codeit.kr/api/employees');

하지만 Promise 객체 앞에 await 키워드를 쓰면 Promise의 상태가 fulfilled 또는 rejected가 될 때까지 기다린다. (Promise 객체의 값을 가져오려면 항상 앞에 await을 붙여야 한다.)

  • Fulfilled(비동기 작업 성공적으로 완료)가 되면 Promise 객체의 결괏값을 리턴한다.
  • Rejected(비동기 작업 실패)가 되면 Promise 객체의 결괏값(오류)을 throw한다.

promise가 Fulfilled되는 경우로 생각해보자.

response 변수에 할당

비동기 작업이 성공해서 Fulfilled 상태가 되면, 비동기 작업의 성공 결과. fetch의 경우 리스폰스를 promise의 결과값으로 갖게 된다. 그리고 await은 promise의 결과값을 꺼내서 리턴해준다. 그래서 리스폰스가 리스폰스 변수에 할당되는 것이다.

response.json 역시 promise를 리턴하는데, 마찬가지로 처음에는 promise가 pending 상태지만, 앞에 await이 있기 때문에 파싱 작업이 끝날 때까지 기다린다. 파싱 작업이 끝나면 promise는 Fulfilled 상태가 되고, 성공 결과. 즉, 파싱된 데이터를 결과값으로 갖게 된다. 그리고 이 결과 값이 데이터에 할당되는 것이다.

data 변수에 할당

그래서 데이터를 출력하면 직원 데이터가 잘 출력된다.

출력된 직원 데이터

‼️ 결론 : Promise를 리턴하는 표현식이 있다면 그 앞에 await 키워드를 써서 결과값을 받아 올 수 있다. await을 쓰면 promise가 fulfilled 상태가 될 때까지 기다렸다가 결과값을 돌려주기 때문이다.

promise를 리턴하는 표현식 앞에 작성된 await

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

async 함수 사용시 주의사항  (0) 2024.12.23
async 함수  (0) 2024.12.23
Promise  (0) 2024.12.22
콜백의 한계점(Callback Hell)  (0) 2024.12.22
비동기 함수  (2) 2024.12.22

+ Recent posts