본문 바로가기
개발 공부 일지/JavaScript

자바스크립트 - Promise

by yelimu 2024. 7. 23.

Promise  : 비동기 작업이 완료되면 값을 알려주는 객체 

일단 Promise 를 돌려주고 나중에 작업이 완료되면 결과값을 Promise에 채워 넣어줌 

 

두 가지 방법으로 사용한다

.then() 메소드 + 콜백

async, await 문법


fetch함수는 Promise객체를 리턴한다

 

fetch() 함수는 첫번째 인자로 URL, 두번째 인자로 옵션 객체를 받고, Promise 타입의 객체를 반환합니다.

반환된 객체는, API 호출이 성공했을 경우에는 응답(response) 객체를 resolve하고, 실패했을 경우에는 예외(error) 객체를 reject합니다.

 

비동기 작업이 끝나면 Promise가 결과값을 알려주는데, 그 결과값을 받아오려면 promise를 리턴해주는 부분 앞에  await 를 써주면 된다 

const reponse = await fetch('https://~');

console.log(response);

 

Promise가 reponse에 바로 할당되지 않고fetch작업이 완료될때까지 기다렸다가 fetch 값이 reponse에 할당됨 reponse 데이터는 보기 어렵기때문에 파싱해야 한다 -> json 메소드 사용

 

const data = reponse.json();consolelog(data);

 

json도 작업이 오래 걸려서 promise를 반환함 

 

const data = await reponse.json();

로 써주면 데이터가 잘 출력됨

 


Promise 상태 

pending(기다림) / fullfilled(성공) / rejected(실패)

 

await을 안써주면 promise를 출력했을때 {<pending>} 상태가 출력됨  

await을 써주면 fulfilled 나 rejected가 될때까지 기다렸다가 작업이 완료되면 결과를 반환한다.


async 함수 안에서만 await 를 쓸 수 있다 

함수 앞에 async 붙여준다

 

arrow 함수 쓸때는 async 위치 주의

main.js에서 코드 순서대로 진행하다가 printEmployees함수를 만나면 모듈 안에서 다시 코드 순서대로 진행

await을 만나면 Promise상태가 fulfilled가 될때까지 다시 main.js의 코드를 실행한다.


효율적인 비동기 코드 짜기 

함수 선언 시 반복문 안에서 await 를 반복하게되면 대기중에 함수 밖으로 나갓다가 들어오는 동작을 반복하게되어 시간이 오래 걸림

반복문 안에서 함수를 실행하면 더 빠르게 동작이 가능하다. 하지만 순서는 섞일수있다.

 

▼ id : 1~10 까지의 정보를 가져오는 코드

출력을 반복하는 함수
반복문 안에서 함수 실행


async는 항상 Promise를 리턴한다

promise에서 결과값을 받아오고싶으면 await 를 써줘야함 

 

async 는 함수 안에 await가 있기 때문에 = 오래 걸리는 작업을 처리 = 값을 받아오는데 오래걸린다.

그래서  Promise {<pending>} 값을 일단 리턴하고,

함수의 바디가 다 실행되고 리턴값이 정해지면 상태를 fullfilled로 바꾸고 함수의 리턴값을 Promise에 채워준다.

 

[실습 예제]

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

async function getInterviews() {
  const response = await fetch('https://learn.codeit.kr/api/interview-results');
  const interviews = await response.json();
  return interviews;
}


function addNewEmployee(employees, interview) {
  const { name, department } = interview;
  const newEmployee = {
    id: employees.length + 1,
    name,
    department,
    email: `${name}@codeitmall.kr`,
  };
  employees.push(newEmployee);
}

면접을 통과한 사람들을 직원 데이터에 추가하기 & 직원 데이터 출력하기

 

const employees = await getEmployees();            함수의 리턴값을 변수에 할당하기  + async 함수이므로 await 사용
const interviews = await getInterviews();               

 

//ver1
for (let i = 0; i < interviews.length; i++){ 
  if (interviews[i].result === "pass"){
    addNewEmployee(employees, interviews[i]);
  }
}

//ver2 

interviews.forEach((interview) => {
  if (interview.result === 'pass'){
    addNewEmployee(employees, interview);
  }
})

console.log(employees);

 

for 문을 써도 되고 forEach를 써도 되는데 

나는 아직 forEach보다는 for문이 익숙하다 ㅜ ㅜ 

arr.forEach() : 배열의 각 요소에 동일한 반복을 수행함


try catch(error)문으로 오류 처리

 

비동기 코드를 실행하는 동안 오류 발생하면

Promise는 Rejected 상태를 갖게 된다 & 발생한 오류를 결과값으로 갖는다


.then() 메소드 : Promise 객체의 메소드이기도 하고, Promise를 리턴하기도 한다 

그럼 그 뒤에 또 then 을 붙여 쓸수 있다.

printEmployees() 함수를 then 문법으로 바꾸기

변수 response와 data를 별도로 선언하지 않았다

 

Promise chain

.then() 안에 있는 콜백의 리턴 값은 다음 콜백의 파라미터로 전달된다고 생각하기 

(사실은 중간에promise가 있지만..)

 

작동 방식

1. 

fetch -> Promise를 리턴 (A) (pending 상태)

.then -> Ptomise를 리턴 (B) 

.then -> Ptomise를 리턴 (C)

 

2.

기다리다가 리퀘스트가 완료되서 리스폰스가 돌아오면 fulfilled상태가 된다 (A)

 

3.

첫번째 then 메소드에 등록된 콜백이 실행된다

프로미스의 결과값인 리스폰스(A)가 then 메소드의 파라미터로 전달된다

 

4. 

콜백이 프로미스를 리턴하면 (D) 

then 메소드가 리턴한 프로미스(B)도 동일한 결과값을 갖게 된다

 

콜백이 프로미스가 아닌, 값을 리턴하면 (1, 'foo')

hen 메소드가 리턴한 프로미스(B)는 fullfilled 상태가 되고 리턴값 = 결과값(1, 'foo')로 갖게됨 

위 코드에서는 파싱된 데이터를 갖게된다

 

5. 

B 가 fullfilled가 되어서 다음 then 메소드의 콜백이 실행된다 

파싱 결과를 첫번쨰 파라미터로 전달한다

콜백 = 데이터를 출력 & console.log 를 리턴 

console.log는 undefined를 리턴하므로 결국 콜백은 undefined를 리턴한다

Promise C는 fullfilled가 되고 undefined 값을 갖게 된다 

마지막 프로미스는 사용하지 않을거라 신경쓰지 않아도 된다~


 

(menus) => getRandomElement(menus) 이 코드는 동기 코드(응답을 기다리지 않고 바로 실행) 이므로 

따로 then() 으로 감싸지않고 콜백 본문에 이어서 써준다


 

리스폰스 : 서버가 보내주는 응답

fetch : 서버로 request를 보내고 response를 받는 함수, promise의 객체 

then 메소드 : reponse가 왔을때 실행할 콜백 


.catch(error) : 에러 발생 시 동작

.finally() : 마지막에 반드시 동작하는 코드

양쪽 코드는 같은 동작을 한다.

각각의 메소드는 모두 Promise를 리턴한다 

잘못된 url 값을 받으면 fetch의 promise는 오류를 돌려받고 

그 다음 then 메소드는 앞의 promise가 fullfilled가 되면 콜백함수를 실행하는데 rejected이므로 아무 콜백을 실행하지 않음 & 이전 promise와 같은 상태(rejected)를 갖게 된다 (전파됨) 

 

catch 메소드는 이전 promise가 rejeceted일때 콜백을 실행한다. 

= console.log -> promise는 fullfilled가 되고, undefined값을 갖는다 

 

finally 메소드는 이전 promise가 더이상 pending 상태가 아니면(fullfilled거나 rejected이면) 콜백을 실행한다. 

= console.log -> promise는 fullfilled가 되고, undefined값을 갖는다 

 


Promise.all() 메소드 -> Promise를 반환한다 

여러 promise을 동시에 기다릴때 사용 

 

await 을 쓰면 getEmployee 함수가 완전히 끝날때까지 기다렸다가 다음 줄로 넘어간다. 

-> 리퀘스트 보내고, 파싱하는 작업을 순차적으로 하게됨

빈 배열 promises를 만들어주고 요소 하나씩 배열에 넣어줌(push)

이렇게 하면 await을 하지 않기때문에 리퀘스트를 병렬적으로 보내게 된다 

 

배열에 promise 객체들이 들어가게 된다 

왜냐면 함수에서 return하는 값이 .json() 해서 나온 값이므로 

Promise.all() 메소드 -> Promise를 반환한다

각 프로미스가  fullfilled가 되면 promise.all가 리턴하는 promise도 각 성공값의 배열을 promise로 받는다

배열 내 프로미스 중 하나라도 rejected가 되면  promise.all가 리턴하는 promise도 rejected 상태가 됨

rejected 된 프로미스의 오류를 받는다 

promise.all의 결과값을 반환하려면 await 를 써서 변수에 할당