[JavaScript] 비동기

2022. 7. 27. 10:05WEB/Javascript

# callback

const printString = (string) =>{
  setTimeout(
    ()=>{
      console.log(string)
    },
    Math.floor(Math,random() * 100) + 1
  )
}

const printAll = () =>{
  printString("A");
  printString("B");
  printString("C");
}

 

- 위 코드의 결과는 실행시마다 달라진다. 비동기 함수이지만 순서를 제어할수가 없다.

const printString = (string, callback) =>{
    setTimeout(
        ()=>{
            console.log(string);
            callback();
            console.log(`${string}종료`)
        },
        Math.floor(Math.random() * 1000) + 1
    )
}

const printAll = () =>{
    printString("A", ()=>{ 
        printString("B", () =>{
            printString("C",()=>{})
        });
    });
};


- 위 코드는 콜백함수를 이용하여 순서를 제어한다. 좋은 방법일것 같지만 코드가 물결치듯이 가독성이 좋지 않다. 이것을 'callback hell' 이라고도 불른다.
-  callback을 통한 비동기처리방식은 에러처리에 한계가 있다.
- callback함수는 이벤트가 발생하면 'Task queue'로 이동한 뒤 호출 스택이 비어졌을때 호출스택으로 이동되어 실행된다.
- setTimeout함수는 비동기 함수이므로 콜백함수 실행완료전 종료되어 호출스택에서 제거된다. 
- 이후 setTimeout의 콜백함수는 호출스택이 비어졌을 때 이동되어 실행된다.
- 이상황에서는 setTimeout함수는 제거된상태이므로 setTimeout함수의 콜백함수를 호출한 것은 논리적으로 setTimeout함수가 아니라고 봐야된다.
- 따라서 setTimeout 함수의 콜백 함수 내에서 발생시킨 에러는 catch 블록에서 캐치되지 않아 프로세스는 종료된다.

try {
  setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
  console.log('에러를 캐치하지 못한다..');
  console.log(e);
}


# Callback error handling Design
- try catch로 안되는 에러처리를 위한 디자인패턴

const somethingGonnaHappen = callback =>{
  waitingUntillSomethingHappens()	//4, 8번째의 결과를 반환

  if(isSomethingGood){
    callback(null, something);
  }

  if(isSomethingBad){
    callback(something, null);
  }
}

somethingGonnaHappen((err,data) => {
  if(err){
    console.log("ERR!!");
    return;
  }
  return data;
})



 

# Promise

- Promise는 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는다.

- Promise는 비동기 함수를 실행할 콜백 함수를 인자로 전달 받고 이 콜백함수는 resolve와 reject함수를 매개변수로 같는다.

- pending, fulfilled, rejected 상태를 가지고 각각 비동기처리가 수행전, 성공, 실패인 상태를 뜻함. 즉 resolve,reject함수가 호출되거나 안된상태이다.

# Promise 객체 생성시 발생하는 일
1. Promise는 인스턴스 생성처럼 new 키워드를 통해 하나의 객체를 생성한다.
2. Promise는 하나의 콜백함수(비동기 콜백함수가 아님)를 인자로 받는다.
3. new Promise가 생성되는 즉시인자로 받아지는 함수도 즉시 실행되며,그래서 이 함수를 executor, 실행자 함수라고도 부른다.
     3.5 executor는 사용자가 직접 구현을 해서 resolve나 reject를 실행하도록 만들수 있다.
const { readFile } = require("fs");

const getDataFromFilePromise = filePath => {
  return new Promise((resolve, reject) => {
    readFile(filePath, "utf8", function (err, text) {
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  });
}


4. 해당 실행자(executor) 함수는 또 다시 2개 함수(resolve, reject)를 인자로 받는다.
5. 실행자 함수가 실행되고 작업이 성공했을 시에는 그 성공 값을 인자로 resolve 함수를 호출하고,만약 비동기 작업이 실패했을 시에는 그 실패 값을 인자로 reject 함수를 호출한다.

 

  • Promise의 처리 메소드
    • Promise.prototype.catch() : 반환된 Promise의 메서드가 실패했을때의 경우만 반환
    • Promise.prototype.then() : 인자는 2개를 받고 각각 Promise객체의 결과가 resolve일때, reject일 경우 이다.
      • then()의 반환값이 값인경우엔 Promise.resolve(<핸들러에서 반환한 값>) 을 반환하는 것과 같다.
      • then()의 반환값이 Promise인경우엔 Promise로 감싸지않고 반환한다.
    • catch(), then()의 return은 Promise를 반환한다. (Promise체인에 활용)
const getDataFromFilePromise = filePath => { return new Promise((resolve, reject) => {
  fs.readFile(filePath,"utf-8",(err,data)=>{
    if(err) reject(err);
    else resolve(data);
  });

 

  • Promise의 에러처리
    • then()이나 catch()를 통해서 에러처리를 하는데 then()에서의 에러처리는 해당 Promise를 다루는 도중에만 에러처리가 가능하다. then체인 마지막에 catch를 넣어주면 then에서 일어나는 에러를 catch에서 잡을수 있다. 물론 가독성도 훨씬 좋아진다.
promiseAjax('https://jsonplaceholder.typicode.com/todos/1')
  .then(res => console.xxx(res))
  .catch(err => console.error(err)); // TypeError: console.xxx is not a function

 

  • Promise chaining
    • 비동기 함수의 처리결과를 가지고 다른 비동기함수를 호출해야하는 경우 함수의 호출이 중첩되어 '콜백헬'같은 형태가 나타난다. 마찬가지로 Promise도 'Promise hell'형태를 띌수 있다.   
  /* lexical scope 활용 */
  
  getDataFromFilePromise(user1Path)
    .then((user1) => {
      return getDataFromFilePromise(user2Path)
      .then(user2 => {
        return [JSON.parse(user1), JSON.parse(user2)];
      })
    })  //promise hell을 해결하기 위해 Promise.all이 등장(여러가지 이유중 하나)

 

  • then()의 콜백함수의 비동기적 실행
    • then과 catch를 사용하여 Promise객체를 받고 then의 콜백함수(핸들러 함수)를 실행할때 전달인자로 받을 수 있다.
    • 전달인자를 받고 실행되는 핸들러 함수는 비동기적으로 실행이 된다.
    • 여기서 말하는 핸들러 함수는 then이나 catch의 리턴값이다.
// 이행한 프로미스를 받으면 'then' 블록도 바로 실행되지만,
// 핸들러는 아래 console.log에서와 같이 비동기적으로 실행됨
const resolvedProm = Promise.resolve(33);

let thenProm = resolvedProm.then(value => {
    console.log("이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 " + value + "입니다.");
    return value;
});
// thenProm의 값을 즉시 기록
console.log(thenProm);

// setTimeout으로 함수 실행을 호출 스택이 빌 때까지 미룰 수 있음
setTimeout(() => {
    console.log(thenProm);
});

 

 

  • Promise.all
    • 해당메서드의 인자로 Promise객체를 요소로하는 배열을 받는다.
    • 전달받은 배열에있는 Promise객체를 병렬로 처리하고 resolve값을 배열로 반환한다.
    • 모든 Promise객체가 종료될때까지 기다렸다가 결과를 반환하고 하나라도 실패가있으면 reject를 반환한다.
    • 호출한순서대로 순서가 보장된 객체를 반환한다.
 /*
  Promise.all
  전달인자 Promise 객체 배열
  반환 값은 resolve()값들이 들어있는 배열

  fetch의 반환값은 Promise이고 해당 Promise가 실행되어 성공시 Response객체를 반환함.
  json()의 반환값은 Promise객체
  */
  
  const newsURL = "뉴스관련URL"
  const weatherURL = "날씨관련URL"
  return Promise.all([
    fetch(newsURL),
    fetch(weatherURL)
  ])
    .then(([newsResponse, weatherResponse]) => {
      return Promise.all([newsResponse.json(), weatherResponse.json()])
    })
    .then(([json1, json2]) => {
      return {
        news: json1.data,
        weather: json2
      }
      
      /* 또는 */
      
       let urls = [newsURL,weatherURL];
  let temp = urls.map((url)=>{return fetch(url)});
  Promise.all(temp)
  .then(responses => { return responses.map((response => {return response.json()}));})
  .then((arr) => {  return Promise.all(arr)})
  .then(resultarr =>{ console.log(resultarr); return resultarr})
let names = ['iliakan', 'Violet-Bora-Lee', 'jeresig'];

let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
  .then(responses => {   //responses에는 fetch의 return값인 Response객체들이 있음
    // 모든 응답이 성공적으로 이행되었습니다.
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // 모든 url의 응답코드가 200입니다.
    }

    return responses;
  })
  // 응답 메시지가 담긴 배열을 response.json()로 매핑해, 내용을 읽습니다.
  .then(responses => Promise.all(responses.map(r => r.json())))
  // JSON 형태의 응답 메시지는 파싱 되어 배열 'users'에 저장됩니다.
  .then(users => users.forEach(user => alert(user.name)));
  1. Promise.all(promises) – 모든 프라미스가 이행될 때까지 기다렸다가 그 결과값을 담은 배열을 반환합니다. 주어진 프라미스 중 하나라도 실패하면 Promise.all는 거부되고, 나머지 프라미스의 결과는 무시됩니다.
  2. Promise.allSettled(promises) – 최근에 추가된 메서드로 모든 프라미스가 처리될 때까지 기다렸다가 그 결과(객체)를 담은 배열을 반환합니다. 객체엔 다음과 같은 정보가 담깁니다.
    • status: "fulfilled" 또는 "rejected"
    • value(프라미스가 성공한 경우) 또는 reason(프라미스가 실패한 경우)
  3. Promise.race(promises) – 가장 먼저 처리된 프라미스의 결과 또는 에러를 담은 프라미스를 반환합니다.
  4. Promise.resolve(value) – 주어진 값을 사용해 이행 상태의 프라미스를 만듭니다.
  5. Promise.reject(error) – 주어진 에러를 사용해 거부 상태의 프라미스를 만듭니다.

 

# async,  await

- async 함수를 실행하게 되면 무조건 Promise 객체가 반환된다.
- async 함수 내에서 return값은 반환된 Promise 객체의 결과(resolve)값이다.

- await는 Promise함수가 fulfill되거나 reject될때 까지 async함수를 멈추고 기다림

- await는 Promise객체를 리턴할 경우에만 의미가 있음

- await의 반환값은 Promise를 실행했을 때 resolve의 전달인자값이다.

- Promise가 reject되면 reject된 값을 throw함. await함수를 호출하는 쪽(async가 선언된곳)에서 try catch로 오류를 잡음

- Promise의 syntactic sugar임