본문 바로가기
항해99/온보딩 스터디

[JS] 온보딩 3주 4일차 - 동기와 비동기, 콜백지옥과 Promise

by junvely 2023. 3. 23.

🦄 온보딩 3주 4일차 - JS동기와 비동기, 콜백지옥과 Promise 

✨03/23 : 목표 : 비동기 개념에 대한 이해

오늘은 부족했던 자바스크립트 개념에 대해 이해하는 시간을 가지기로 했다.

그 동안 항상 어려워했던 동기와 비동기에 대한 개념, 콜백지옥과 Promise를 사용하는 이유와 방법에 대해 공부해 보았다.


01 동기와 비동기

 

1. 동기와 비동기

1) 동기(scynchrounous) : JS는 동기적이다  =>  hoisting이 된 이후부터, 코드가 우리가 작성한 순서에 맞춰서 하나하나씩 자동적으로 실행된다. 

2) 비동기(asynchronous) : 언제 코드가 실행될지 예측할 수 없는 것을 말한다.

setTimeout(() => console.log("2"), 1000); //콜백을 이용한, 비동기적

3) 동기적 콜백과 비동기적 콜백

// Synchronous callback(동기적 콜백) : 즉각적, 동기적으로 실행한다.
function printImmediately(print) {
  print();
} 
printImmediately(() => console.log("hello"));

// Asynchronous callback(비동기적 콜백) : 언제 실행될지 예측할 수 없다.
function printWithDelay(print, timeout) {
  setTimeout(print, timeout); 
}
printWithDelay(() => console.log("async callback"), 2000);

4) 비동기적으로 처리하는 이유

- 네트워크 통신이나 파일 읽기 등 무겁고 오래걸리는 로직 등을 동기적으로 실행하게 되면, 시간이 오래걸릴 수록 그 시간동안 다음 코드가 실행되지 않는다. 따라서 네트워크 통신 등 무서운 기능을 수행할 때에는 비동기적으로 처리해야 한다. 

 

2. 콜백 지옥

콜백함수 연속적으로 체이닝해서 콜백함수 안에서 계속해서 비동기적으로 처리하는 콜백지옥..

class UserStorage {
  loginUser(id, password, onSuccess, onError) { // 성공시, 실패시 실행할 콜백
    setTimeout(() => {
      // 실제 백엔드가 없기 때문에 login시 걸리는 시간처럼 가정해 본 것
      if (
        (id === "ellie" && password === "dream") ||
        (id === "coder" && password === "academy")
      ) {
        onSuccess(id); 
      } else {
        onError(new Error("not found")); 
      }
    }, 2000);
  }

  // role : 역할 > 사용자 개인마다의 AD,guest역할 등의 정보를 서버에 요청해서 받아오는 함수
  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === "ellie") {
        onSuccess({ name: "ellie", role: "admin" }); 
      } else {
        onError(new Error("no access")); 
      }
    }, 1000);
  }
}

// 사용자의 데이터를 서버에게서 받아오는 class 작성
const userStorage = new UserStorage(); // class 생성
const id = prompt("enter your id");
const password = prompt("enter your password");

// 콜백지옥..
userStorage.loginUser(
  id,
  password,
  (user) => {
    userStorage.getRoles(
      user,
      (userWithRole) => {
        alert(
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      (error) => {
        console.log(error);
      }
    );
  },
  (error) => {
    console.log(error);
  }
);

콜백지옥의 단점 

1. 가독성이 최악이다. 비즈니스 로직도 파악하기 어렵다.

2. 에러 발생 시 디버깅이 어려워 유지보수가 힘들다.

 

 

3. 프로미스(Promise)로 콜백지옥 개선하기

Promise : JS에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 Object이다. 프로미스는 정해진 장시간의 기능을 수행하고 나서, 정상적으로 기능을 수행하였다면 성공의 메세지와 함께 처리된 결과값을 전달하고, 기능 수행 중 예상치 못한 문제 발생 시 에러를 전달해 준다.

=> 콜백지옥 대신, 비동기적인 것을 처리할 때 간편하게 사용 가능하다.

//프로미스 생성
const promise = new Promise((resolve, reject) => {
  console.log("doing something..."); // 네트워크 통신로직
  setTimeout(() => {
    resolve("ellie");
    // reject(new Error("no network"));
  }, 2000);
});
//프로미스 실행
promise
  .then((value) => {
    console.log(value);
  }).catch((error) => {
    console.log(error);
  })
  // finally > 최근에 추가됨 > 성공과 실패 여부와 상관없이 마지막에 무조건 호출되어지는 함수
  .finally(() => {
    console.log("finally");
  });

=> .then(value=>{}) : 프로미스에서 성공 시 resolve의 리턴값으로 'ellie'를 받아와 콜백함수를 실행한다.

=> .catch(error=>{}) : 프로미스에서 실패 시 error를 받아 실행한다.

=> .finally(()=>{}) : 성공과 실패 여부와 상관없이 무조건 마지막에 실행한다.

 

 

4. 프로미스(Promise) 체이닝

// 서버에서 숫자를 받아오는 promise 생성
const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then((num) => num * 2) // then 은 값을 바로 전달할 수도 있고, 
  .then((num) => num * 3)
  .then((num) => {
    // 6
    return new Promise((resolve, reject) => { //다른 비동기인 promise를 전달할 수 도 있다.
      setTimeout(() => resolve(num - 1), 1000); // 5
    });
  })
  .then((num) => console.log(num)); // 5

 

4. 에러 핸들링

  // 서버에서 닭을 받아오고 > 달걀을 받아오고 > 요리하는 과정
const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("🐔"), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🥘`), 1000);
  });
  
  getHen()
  .then(getEgg) // .then((hen) => getEgg(hen)) value 한개 전달 시 생략 가능
  .catch((error) => {
    // 해당 부분에 발생하는 에러 바로 캐치하여 잡아줄 수 있다.
    // 전달되는 promise chain에 문제(error)가 생기더라도 대신 대체해 줄 수 있도록 하기 > 계란을 받아오는데 실패하더라도 promise chain이 실패하지 않도록 하기
    return "🍞";
  })
  .then(cook)
  .then(console.log)
  .catch(console.log); // 마지막에 사용해도 에러난 부분의 error 띄워줌

 

 

5. 콜백지옥 Promise 사용하여 깔끔하게 정리하기 > 콜백으로 작성된 부분 대신 promise로 정리

"use strict";

// 콜백 지옥
class UserStorage {
  loginUser(id, password) {
    // 콜백 대신 promise 생성
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "ellie" && password === "dream") ||
          (id === "coder" && password === "academy")
        ) {
          resolve(id);
        } else {
          reject(new Error("not found"));
        }
      }, 2000);
    });
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "ellie") {
          resolve({ name: "ellie", role: "admin" });
        } else {
          reject(new Error("no access"));
        }
      }, 1000);
    });
  }
}

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");

userStorage
  .loginUser(id, password) // id, password 정보 가져오기 성공하면,
  .then(userStorage.getRoles) // userStorage.getRoles호출
  .then((user) => alert(`Hello ${user.name}, you have a ${user.role} role`)) // user 정보 가져와서 콘솔 출력
  .catch(console.log); // 에러시 콘솔출력

 

➕ 가장 중요한 async await 사용하기

 

자바스크립트 기초(ES5+)_비동기의 꽃 JavaScript async와 await / 유용한 Promise APIs

[ async · await ] Promise를 조금 더 간편, 간결하게 동기적으로 실행되는 것처럼 보이도록 도와주는 것 > 깔끔하게 Promise 사용하는 법 Promise chaining(.then)을 계속 사용하면 코드가 난잡해지는 것을 좀

junvelee.tistory.com