개발/React

Javascript - 응용(동기&비동기, Promise, async&await, API 호출하기)

잇(IT) 2023. 9. 11. 16:42
728x90
- 동기 & 비동기
- 동기

- Javascript는 코드가 작성된 순서대로 작업을 처리한다.

- 이전 작업이 진행 중 일 때는 다음 작업을 수행하지 않고 기다린다.

- 먼저 작성된 코드를 먼저 다 실행하고 나서 뒤에 작성된 코드를 실행한다.

- 비동기

- 싱글 쓰레드 방식을 이용하면서, 동기적 작업의 단점을 극복하기 위해 여러 개의 작업을 동시에 실행 시킨다.

- 즉, 먼저 작성된 코드의 결과를 기다리지 않고, 다음 코드를 바로 실행한다.

 

function taskA(a,b, cb) {
  // console.log("A 작업 끝");
  setTimeout(() => {
    const res = a + b;
    cb(res);
  }, 3000);
}

function taskB(a, cb){
  setTimeout(() => {
    const res = a * 2;
    cb(res);
  }, 1000);
};

function taskC(a, cb) {
  setTimeout(() => {
    const res = a * -1;
    cb(res);
  }, 2000);
}
taskA(3,4, (res) => {
  console.log("A TASK RESULT : ", res);
});

taskB(7, (res) => {
  console.log("B TASK RESULT : ", res)
});

taskC(10, (res) => {
  console.log("C TASK RESULT : ", res)
});

// 비동기 방식 하나 더

taskA(4, 5, (a_res)=>{
  console.log("A RESULT : ", a_res);
  taskB(a_res, (b_res)=>{
    console.log("B RESULT : ", b_res);
    taskC(b_res,(c_res)=>{
      console.log("C RESULT : ", c_res);
    });
  });
});
console.log("코드 끝");

- 위처럼 task A,B,C를 setTimeout를 통해 비동기 방식으로 딜레이 시간을 부여하게 되면, 1. 동기 방식이라면 taskC가 실행되기 위해선 3초 + (A 메서드 실행시간) + 1초 (B 메서드 실행시간) + 2초 + (C 메서드 실행시간)만큼의 시간을 기다려야 TaskC의 결과를 확인 할 수 있다.

- 하지만 위의 경우 taskB(), taskC()는 각각 1,2 초의 딜레이가 있고 task()A의 경우 딜레이가 3초이기 때문에 taskA()가 완료될 때까지 기다리는 것은 효율적이지 못하다.

- 즉, 이를 해결하기 위해, 비동기 방식으로 동작하는 Javascript는 실행 순서에 상관없이 메서드를 실행하게 된다.

 

taskA(1,2,(a_res)=>{
  console.log("REULST : ", a_res)
  taskB(a_res,(b_res)=>{
    console.log("RESULT : ", b_res)
    taskC(b_res,(c_res)=>{
    console.log("RESULT : ", c_res)
    })
  })
})

- 콜백 지옥

1. 위 코드와 같이 함수의 매개 변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.

 

- 위의 콜백 지옥은 Javascript의 Promise를 통해 해결 할 수 있다.


Promise

- 프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 자바스크립트의 비동기 처리란 '특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성'을 의미한다.

 

- Promise의 3가지 상태 (states)

1. Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

2. Fullfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

3. Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한상태

 

- Pending(대기)

1. new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.

2. new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject이다.

 

- Fuilfilled(이행)

1. 콜백 함수의 인자 resolve를 실행하면 Fulfilled(이행) 상태가 된다.

2. 이행 상태가 되면 then()를 이용하여 해당 메서드의 처리 결과 값을 받을 수 있다.

 

- Rejected(실패)

1. new Promise()로 Promise 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있다

2. reject를 호출하면 실패(Rejected) 상태가 된다.

3. 실패 상태가 되면 실패한 이유를 catch()로 받을 수 있다.

 

- 예제 코드

function isPositiveP(number){ 
    const executor = (resolve, reject) => { // 실행자 
    setTimeout(()=>{ 
      if(typeof number === "number"){ 
        // 성공 -> resolve 
        resolve(number >= 0 ? "양수" : "음수") }
        else{ 
        // 실패 -> reject 
        reject("주어진 값이 숫자형 값이 아닙니다."); } },2000) }

  const asyncTask = new Promise(executor); 
  return asyncTask;
}

  const res = isPositiveP(101);
  res
  .then((res)=>{console.log("작업 성공 : ", res);})
  .catch((err)=>{console.log("작업 실패 : ", err)});

- Promise 사용의 안좋은 예

function taskA(a,b){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
    const res = a+b;
    resolve(res);
  }, 3000);
  });
}
  
function taskB(a){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      const res = a * 2;
      resolve(res);
    }, 1000)
  })
};

function taskC(a){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      const res = a * -1;
      resolve(res);
    }, 2000)
  })
}

taskA(5,1).then((a_res)=>{
  console.log("A RESULT : ", a_res);
  taskB(a_res).then((b_res)=>{
    console.log("B RESULT : ", b_res);
    taskC(b_res).then((c_res)=>{
      console.log("C RESULT : ", c_res);
    })
  })
})

- 위 코드는 Promise를 사용하였지만 여전히 콜백 지옥과 비슷한 형태로 메서드를 호출하고 있다.


- Promise then 체이닝

getData(userInfo)
  .then(parseValue)
  .then(auth)
  .then(diaplay);
  
  
  .....
  
  var userInfo = {
  id: 'test@abc.com',
  pw: '****'
};

function parseValue() {
  return new Promise({
    // ...
  });
}
function auth() {
  return new Promise({
    // ...
  });
}
function display() {
  return new Promise({
    // ...
  });
}

- 위와 같이 여러개의 Promise를 .then을 통해 간결하게 나타낼 수 있다.

 

function taskA(a,b){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
    const res = a+b;
    resolve(res);
  }, 3000);
  });
}
  
function taskB(a){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      const res = a * 2;
      resolve(res);
    }, 1000)
  })
};
  
function taskC(a){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      const res = a * -1;
      resolve(res);
    }, 2000)
  })
}

// then 체이닝 (결과 값을 변수에 넣어서 사용한다.)

const bPromiseResult = taskA(5,1).then((a_res)=>{
  console.log("A RESULT : ", a_res);
  return taskB(a_res);
});

bPromiseResult.then((b_res)=>{
  console.log("B RESULT : ", b_res);
  return taskC(b_res);
}).then((c_res)=>{
  console.log("C RESULT : ", c_res);
})

- 위 코드 위에서 Promise를 사용하였지만 콜백 지옥과 유사한 형태를 띄었던 코드를 return값을 Promise 객체로 지정하여 then을 통해 연결 할 수 있도록 수정하였다.


- Promise의 에러 처리 방법

1. then()의 두 번째 인자로 에러를 처리하는 방법

getData().then(
  handleSuccess,
  handleError
);

2. catch()를 이용하는 방법

getData().then().catch();

 

function getData() {
  return new Promise(function(resolve, reject) {
    reject('failed');
  });
}

// 1. then()의 두 번째 인자로 에러를 처리하는 코드
getData().then(function() {
  // ...
}, function(err) {
  console.log(err);
});

// 2. catch()로 에러를 처리하는 코드
getData().then().catch(function(err) {
  console.log(err);

- 가급적 프로미스 에러 처리는 catch()를 사용하는 것이 좋다.


- async & await

- 비동기 작업을 매번 콜백 지옥에서 벗어나기 위해 Promise 객체를 작성하는 것도 물론 좋은 방법이지만 Javascript에서는 Promise 객체를 생성하는 것 조차 줄이는 방법이 있다. async를 사용하는 것이다.

 

- async

- async 키워드는 함수를 선언할 때 붙여줄 수 있다. async 함수는 비동기 작업 그 자체를 뜻한다

1. 함수에 async를 붙인다

2. new Promise... 부분을 없애고 executor 본문 내용만 남긴다.

3. resolve(value); 부분을 return value;로 변경한다.

4. reject(new Error(...)); 부분을 throw new Error(...);로 수정한다.

 

// 기존
// function startAsync(age) {
//   return new Promise((resolve, reject) => {
//     if (age > 20) resolve(`${age} success`);
//     else reject(new Error(`${age} is not over 20`));
//   });
// }

// async로 변경
async function startAsync(age) {
  if (age > 20) return `${age} success`;
  else throw new Error(`${age} is not over 20`);
}

const promise1 = startAsync(25);
promise1
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });

const promise2 = startAsync(15);
promise2
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });

- 여기서 알아야 할 것은 async 함수의 리턴 값은 무조건 Promise이다.

 

* 모든 비동기 동작을 async 함수로 만들 수 있는 건 아니다

- setTimeout 함수 안의 resolve가 있을 경우 setTimeout이 resolve를 호출하기 때문에 (3. resolve(value); 부분을 return value;로 변경한다.)를 보장하지 못한다.

- 때문에 setTimeout의 경우는 async 없이 사용해야 한다.

 

- await

- await는 Promise가 fulfilled가 되든지 rejected가 되든지 아무튼 간에 끝날 때까지 기다리는 함수다.

- await는 async 함수 내부에서만 사용할 수 있다.

function setTimeoutPromise(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay));
}
// 결과값이 필요없는 경우 위와 같이 작성할 수 있으면 결과값은 undefined이다.

async function startAsync(age) {
  if (age > 20) return `${age} success`;
  else throw new Error(`${age} is not over 20`);
}

async function startAsyncJobs() {
  await setTimeoutPromise(1000);
  const promise1 = startAsync(25);

  try {
    const value = await promise1;
    console.log(value);
  } catch (e) {
    console.error(e);
  }

  const promise2 = startAsync(15);

  try {
    const value = await promise2;
    console.log(value);
  } catch (e) {
    console.error(e);
  }
}

startAsyncJobs();

- startAsyncJobs 함수를 새로 만들었습니다. 이 함수 내에서 await 을 사용하기 위해 async 함수로 정의내린 후, 코드의 마지막 부분에서 호출함으로써 비동기 작업을 시작했습니다. 기존의 then 과 catch 하던 작업들은 모두 이 함수 내에 있다.

 

1. 문법적으로 await [[Promise 객체]] 이렇게 사용합니다.
2. await 은 Promise 가 완료될 때까지 기다립니다. 그러므로 setTimeoutPromise 의 executor 에서 resolve 함수가 호출될

때까지 기다립니다. 그 시간동안 startAsyncJobs 의 진행은 멈춰있습니다.
3. await 은 Promise 가 resolve 한 값을 내놓습니다. async 함수 내부에서는 리턴하는 값을 resolve 한 값으로 간주하므로,

${age} success 가 value로 들어온다는 점을 알 수 있습니다.
4. 해당 Promise 에서 reject 가 발생한다면 예외가 발생합니다. 이 예외 처리를 하기 위해 try-catch 구문을 사용했습니다.

reject 로 넘긴 에러(async 함수 내에서는 throw 한 에러)는 catch 절로 넘어갑니다. 이로써 익숙한 에러 처리 흐름으로 진행

할 수 있습니다.

 

- await 은 then 과 catch 의 동작을 모두 자기 나름대로 처리합니다. 그래서 async 함수 내에서 then, catch 메소드의 존재를 잊게 할 수 있다.


- API 호출
async function getData(){
  let rawResponse = await fetch('https://jsonplaceholder.typicode.com/posts')
  let jsonResponse = await rawResponse.json()
  console.log(jsonResponse);
}

getData();

let response = fetch('https://jsonplaceholder.typicode.com/posts')
//fetch는 성공 객체 자체를 보낸다.
.then((res)=>{
  console.log(res)
});

- fetch()는 웹페이지에서 리소스를 가져오기 위한 기보적인 API이다. 

 

fetch(url [, options])
  .then(response => response.json()) // 또는 다른 방법으로 데이터를 처리
  .then(data => {
    // 데이터를 사용하는 코드
  })
  .catch(error => {
    // 에러 처리
  });

1. url : 가져올 리소스의 URL이다.

2. options : 요청에 대한 다양한 옵션을 설정하는 객체이다.

 

- 응답 객체(Response)는 여러 속성과 메서드를 가지고 있어서 데이터를 가져오고 처리할 수 있다. 위 예제에서는 response.json() 메서드를 사용하여 JSON 형식의 응답 데이터를 가져온다. 다른 메서드로는 response.text() (텍스트 데이터 가져오기) 와 같은 예가 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

- 참고 블로그 https://springfall.cc/article/2022-11/easy-promise-async-await

728x90