내용 정리 JS - 비동기 처리

이유승·7일 전
0

내용 정리

목록 보기
31/31
post-thumbnail

1. 비동기 처리?

  • 작업이 완료될 때까지 기다리지 않고 다음 작업을 계속 수행하는 방식.

  • 자바스크립트는 단일 스레드로 동작한다.

  • 작업은 순차적으로 진행되지만, 작업의 결과를 기다렸다가 다음 작업을 실행하는게 아니라..

  • 뒤에 있는 작업을 바로 실행시켜버린다!

  • 그렇기에 HTTP 요청 등의 이유로 요청의 결과가 뒤늦게 반환되는 경우에는..

  • 결과값이 반환되어 올 때에는 이미 다음 작업이 실행되어버린다.

  • 이럴 때 필요한 것이 비동기 처리.

동기와 비동기



2. 비동기 처리의 방법들

콜백 함수 (Callback)

function fetchData(callback) {
  setTimeout(() => {
    callback("데이터를 가져왔습니다!");
  }, 1000);
}

fetchData((data) => {
  console.log(data);
});
  • 특정 작업이 완료된 후 실행할 코드를 함수로 전달하는 방식.

  • 완벽해보이지만.. 작업이 복잡해지면 치명적인 단점이 하나 부각된다.

콜백 지옥(Callback Hell)!

  • 비동기 작업을 처리하기 위해 중첩된 콜백 함수가 다단계로 계속 이어지는 코드 구조.
function fetchUser(userId, callback) {
  setTimeout(() => {
    console.log("사용자 정보 가져오기 완료");
    callback(null, { id: userId, name: "홍길동" });
  }, 1000);
}

function fetchOrders(user, callback) {
  setTimeout(() => {
    console.log(`${user.name}의 주문 목록 가져오기 완료`);
    callback(null, ["주문1", "주문2", "주문3"]);
  }, 1000);
}

function fetchOrderDetails(order, callback) {
  setTimeout(() => {
    console.log(`${order}의 상세 정보 가져오기 완료`);
    callback(null, { id: order, details: "상세 내용" });
  }, 1000);
}

// 콜백 지옥의 구현
fetchUser(1, (error, user) => {
  if (error) {
    console.error("사용자 정보를 가져오는 중 에러 발생:", error);
    return;
  }
  fetchOrders(user, (error, orders) => {
    if (error) {
      console.error("주문 목록을 가져오는 중 에러 발생:", error);
      return;
    }
    fetchOrderDetails(orders[0], (error, orderDetails) => {
      if (error) {
        console.error("주문 상세 정보를 가져오는 중 에러 발생:", error);
        return;
      }
      console.log("최종 결과:", orderDetails);
    });
  });
});
  • 단계적으로 이루어지는 작업이 여러 개 있다고 가정해보자.

  • 이들을 콜백 함수 형태로 이어지게 하면..

  • 함수 뒤에 콜백 함수, 콜백 함수 뒤에 또 다른 콜백 함수, 또 다른 콜백 함수 뒤에 또또 다른 콜백 함수...

  • 가독성은 저하되고, 유지보수가 어려워지고, 에러 핸들링 난이도도 높아진다.



프로미스 (Promise) 객체

  • 콜백 함수의 콜백 지옥 문제를 해결해주기 위해 개발된 개념.

  • 비동기 작업의 완료 또는 실패를 나타내는 객체.

const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("데이터를 가져왔습니다!");
  }, 1000);
});

fetchData
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });
  • .then과 .catch를 통해 체이닝이 가능하여 콜백 함수 방식보다 가독성이 향상되었다.

프로미스 (Promise) 심화

  • Promise는 자바스크립트에서 비동기 작업의 결과를 처리하기 위해 사용하는 객체.

  • 비동기 작업이 성공하거나 실패했을 때 각각의 상태와 결과를 표현할 수 있다. 3가지 상태로 표현된다.
    - Pending (대기): 초기 상태, 비동기 작업이 아직 완료되지 않음.
    - Fulfilled (이행): 비동기 작업이 성공적으로 완료됨. resolve()를 통해 전달된 값이 결과로 제공됨.
    - Rejected (거부): 비동기 작업이 실패함. reject()를 통해 전달된 값이 에러로 제공됨.

// Promise의 기본 사용법
const myPromise = new Promise((resolve, reject) => {
  const success = true;
  
  if (success) {
    resolve("작업이 성공했습니다!");
  } else {
    reject("작업이 실패했습니다.");
  }
});

myPromise
  .then((result) => {
    console.log(result); // 작업이 성공했습니다!
  })
  .catch((error) => {
    console.error(error); // 작업이 실패했습니다.
  });

Promise.all?

  • 여러 개의 Promise를 병렬로 처리할 때 사용되는 메서드.

  • 전달된 모든 Promise가 이행(Fulfilled)되면, 결과값 배열을 반환.

  • 만약 하나의 Promise라도 거부(Rejected)되면, 전체가 거부.

  • 각 Promise의 결과를 순서대로 배열에 담아 반환된다.

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // [1, 2, 3]
  })
  .catch((error) => {
    console.error("에러 발생:", error);  // "에러 발생: 오류 발생"
  });



async/await!

  • ES8에서 도입된 최신 문법, 비동기 코드를 동기 코드처럼 작성할 수 있도록 개발됨.

  • 현대 프론트엔드에서는 특별한 이유가 없는 이상 async/await을 사용한다.

async function main() {
  try {
    const user = await fetchUserData(1);
    console.log(user); // { id: 1, name: "홍길동" }
  } catch (error) {
    console.error(error); // 유효하지 않은 사용자 ID입니다.
  }
}
  • async/await의 동작 원리는..

  • 사실 내부적으로 Promise를 사용한다.

  • Promise를 더 직관적이고 간결하게 사용할 수 있도록 만든 문법이기 때문.

  • 그래서 async 키워드로 선언된 함수는 항상 Promise 객체를 반환해야한다.

  • 그리고 await 키워드는 뒤에 오는 값이 반드시 Promise이어야 한다.



콜백 지옥 해결

Promise 사용

function fetchUser(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("사용자 정보 가져오기 완료");
      resolve({ id: userId, name: "홍길동" });
    }, 1000);
  });
}

function fetchOrders(user) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`${user.name}의 주문 목록 가져오기 완료`);
      resolve(["주문1", "주문2", "주문3"]);
    }, 1000);
  });
}

function fetchOrderDetails(order) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`${order}의 상세 정보 가져오기 완료`);
      resolve({ id: order, details: "상세 내용" });
    }, 1000);
  });
}

fetchUser(1)
  .then((user) => fetchOrders(user))
  .then((orders) => fetchOrderDetails(orders[0]))
  .then((orderDetails) => console.log("최종 결과:", orderDetails))
  .catch((error) => console.error("에러 발생:", error));
  • Promise 객체의 사용으로 콜백 지옥은 해소되고 가독성 등도 향상되었다.

async/await 사용

async function main() {
  try {
    const user = await fetchUser(1);
    const orders = await fetchOrders(user);
    const orderDetails = await fetchOrderDetails(orders[0]);
    console.log("최종 결과:", orderDetails);
  } catch (error) {
    console.error("에러 발생:", error);
  }
}

main();
  • 현재 프론트엔드에서 async/await을 대중적으로 사용하는 이유. 코드의 가독성과 유지보수성이 말도 안되게 향상된다.



번외. then-catch와 try-catch

  • 비동기 처리에서 에러를 처리하는 두 가지 방식.

  • 둘 다 Promise를 기반으로 동작하지만, 코드 작성 방식과 처리 방식에 차이가 있다.



then-catch

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => {
    if (!response.ok) throw new Error("HTTP 에러 발생");
    return response.json();
  })
  .then((data) => {
    console.log("데이터:", data);
  })
  .catch((error) => {
    console.error("에러:", error.message);
  });
  • Promise의 메서드 체이닝을 사용하여 비동기 작업과 에러를 처리하는 방식.

  • then 메서드에서 작업 결과를 처리하고, catch 메서드에서 에러를 처리한다.

  • 코드가 간단하고 체이닝을 통해 여러 작업을 순차적으로 처리하기에 적합하지만, 복잡한 로직에서는 체이닝이 길어지면 가독성이 떨어진다.



try-catch

async function fetchData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    if (!response.ok) throw new Error("HTTP 에러 발생");
    const data = await response.json();
    console.log("데이터:", data);
  } catch (error) {
    console.error("에러:", error.message);
  }
}

fetchData();
  • async/await와 함께 사용하여 비동기 작업의 에러를 처리하는 방식.

  • try 블록 안에서 비동기 작업을 수행하고, catch 블록에서 에러를 처리한다.

  • 동기 코드와 비슷한 방식으로 작성되어 가독성이 좋고, 복잡한 로직을 처리하기 쉽다. 비동기 작업 중간에 조건문 혹은 반복문 등의 로직을 삽입할 수 있는 것도 강점.

  • 다만 async/await 환경 외에는 사용이 불가능하고, 간단한 작업에서는 then-catch보다 코드가 길어질 수 있다.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글