JavaScript - 동기/비동기 정리본

Hunter Joe·2025년 2월 24일
0
post-thumbnail

목차

  1. 동기/비동기
  2. Callback
  3. Promise
  4. async/await
  5. try/catch
  6. fetch & axios

📌동기/비동기

동기(Synchronous)

동기 : 사전적으로 '동시에 일어난다' 라는 의미

프로그래밍에서 동기는 작업이 순차적으로 진행되는 것을 의미한다.
작업이 시작되면 해당 작업이 완료될 때까지 다른 작업을 기다려야 한다.
동기 방식은 호출한 함수 또는 작업이 반환될 때까지 대기하는 동안 실행 흐름이 차단되는 특징이 있다.

장점

  • 동기 방식은 일반적으로 간단하고 직관적인 코드를 작성하기 쉽다.

단점

  • 여러 작업이 동시에 실행되어야 하는 경우 각 작업의 완료를 기다리는 동안 시간이 소요되어 전체 프로세스의 성능이 저하될 수 있다.

  • 한 작업이 지연되면 다른 작업들도 모두 지연되는 문제 또한 발생한다.

비동기 (Asynchronous)

비동기 : 사전적으로 '동시에 일어나지 않는다'라는 의미

프로그래밍에서 비동기는 작업이 독립적으로 진행되는 것을 의미한다.
작업이 시작되면 해당 작업이 완료될 때까지 기다리지 않고 다음 코드를 실행할 수 있다.
비동기 방식은 호출한 함수 또는 작업이 완료될 때까지 기다리지 않고 즉시 다음 코드로 실행 흐름이 이동하는 특징이 있다.

장점

  • 하나의 작업이 완료되기를 기다리지 않고 동시에 여러 작업이 실행할 수 있어 응답성이 향상된다.
  • CPU와 Input/Output (I/O) 리소스를 효율적으로 활용할 수 있어 성능이 좋다.
  • 서버 요청이 많은 웹 애플리케이션에서 UX가 향상된다.

단점

  • 코드의 흐름이 동기 방식보다 이해하기 어려울 수 있다.
    + 동기 방식보다 디버깅이 어렵고, 실행 순서를 예측하기 힘들다.
  • 콜백 지옥과 같은 복잡한 코드 구조가 발생할 수 있다.
  • 비동기 처리 중 발생하는 에러 핸들링이 까다롭다.

비동기 방식은 Callback , Promise, async/await등의 메커니즘을 통해 구현될 수 있다.


📌Callback

콜백 함수는 매개변수로 다른 함수에 전달되는 함수(익명 함수)이다.
특정 동작을 완료한 후 실행되어야 하는 코드를 정의할 때 사용된다.

비동기 콜백 함수

setTimeout(), addEventListener(),setInterval(), fetch(url).then(callback), .json()

동기 콜백 함수

forEach(), map(), filter(), reduce() 등의 배열 메서드

NOTE
콜백 함수가 동기식으로 호출되는지, 비동기식으로 호출되는지 이해하는 것은 Side effect(부수 효과)를 분석할 때 중요

let value = 1;

callbackFn(() => {
  value = 2;
});

console.log(value);
  • 콜백 함수가 동기적으로 실행된다면 console.log(value)는 2를 출력할 것이고
  • 콜백 함수가 비동기적으로 실행된다면 console.log(value)는 1을 출력 한다.

익명 함수

NOTE에서 봤듯이 콜백 함수는 호출 함수에 일회용으로 사용하는 경우가 많아
코드의 간결성을 위해 이름이 없는 '익명의 함수'를 사용한다.

📌Promise

비동기는 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 방식이라고 위에서 말했다.
비동기 작업의 결과에 따라 다른 작업을 실행해야 할 때는 전통적으로 콜백 함수를 사용했다.
하지만 콜백 함수가 많아질수록 코드가 복잡해지고 관리하기 어려워 이를 더 쉽게 처리하고자 Promise가 나오게 되었다.

바로 아래 콜백 지옥과 Promise를 활용한 개선된 비동기 처리를 코드로 확인해보자
↓↓↓

콜백 지옥

function increaseAndPrint(n, callback) {
  setTimeout(() => {
    const increased = n + 1;
    console.log(increased);
    if (callback) {
      callback(increased); // 콜백함수 호출
    }
  }, 1000);
}

increaseAndPrint(0, n => {  // (1) 0을 입력하고 비동기 실행 (1초 뒤 실행)
  increaseAndPrint(n, n => {  // (2) 1을 입력하고 비동기 실행 (1초 뒤 실행)
    increaseAndPrint(n, n => {  // (3) 2를 입력하고 비동기 실행 (1초 뒤 실행)
      increaseAndPrint(n, n => {  // (4) 3을 입력하고 비동기 실행 (1초 뒤 실행)
        increaseAndPrint(n, n => {  // (5) 4를 입력하고 비동기 실행 (1초 뒤 실행)
          console.log('끝!');
        });
      });
    });
  });
});

Promise로 개선한 코드

function increaseAndPrint(n) {
  return new Promise((resolve, reject)=>{
    setTimeout(() => {
      const increased = n + 1;
      console.log(increased);
      resolve(increased);
    }, 1000)
  })
}

increaseAndPrint(0)
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n))
  .then((n) => increaseAndPrint(n))
  .catch(error => { // 에러 핸들링
   console.error("Something went wrong' error.message);
  });

NOTE
.then()문법을 계속 연결한 것을 체이닝 기법이라고 한다.

Promise 객체

Promise객체는 비동기 작업의 최종 완료 또는 실패를 나타내는 결과를 제공하겠다는 '약속'을 반환 한다는 의미에서 Promise라고 지어졌다.

Prosmise 생성 예제

const myPromise = new Promise((resolve, reject) => {
	// 비동기 작업 수행
    const data = fetch('URL');
    
    if(data)
    	resolve(data); 
    else
    	reject("Error"); 
})

비동기 작업이 성공하면 resolve를 실패하면 reject메서드를 호출한다.

Promise의 3가지 상태

  1. Pending : 작업이 완료되지 않은 상태
  2. Fulfilled : 작업이 성공적으로 된 상태
  3. Eejected : 작업이 실패 된 상태

한계점

하지만 Promise도 비동기 처리를 완벽하게 해결하지 못해 Promise 지옥(Promise Hell) 문제가 발생할 수 있었다. 이를 개선하기 위해 ES8에서는 async/await를 도입했다.

📌async/await

async/await가 도입되면서 기존 Promise를 좀 더 편하게 사용할 수 있게 되었다.

NOTE
async/awaitPromise를 완전하게 대체하기 위한 기능이 아닌,
단지 코드 작성 부분에서 가독성을 높이기 위한 추가 기능인 것이다.

주요 특징

  • 비동기 코드를 동기 코드처럼 작성 가능
  • await는 Promise가 해결 될 때까지 기다림
  • try/catch로 가독성 좋은 에러 핸들링

async

async 키워드는 함수 앞에 존재한다.

// Example
async function fn() {
  return 1;
}
const data = fn();
console.log(data);

NOTE
async function의 리턴 값은 항상 Promise 객체이다.

await

await키워드는 .then문법을 개선한 것이다.
await키워드는 async function안에서만 동작한다.

async function getData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  const data = await response.json();
  console.log(data);
}

↑위 코드의 처리 과정을 살펴보자
1. fetch("URL")을 통해 Promise를 실행, response에 응답이 오기를 기다린다.
2. response.json()을 통해 Promise를 실행, data를 기다린다
3. data를 출력한다.

NOTE
.json()은 비동기다.

알고가면 좋은 응답 데이터 분석

async function getData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  const data = await response.json();
  console.log(data);
}

response에 대한 응답으로 아래와 같은 로그를 받았다.

content-type : application/json이라는 것과
body: readalbe이라는 것을 보고 해당 응답은 json으로 된, 그리고 읽을 수 있는 응답이라는 것이다.


이제 아래 try/catch를 통한 에러 핸들링 예제를 배워보자

📌try/catch

try/catch 문법 자체는 ES3(1993년) 부터 존재 했으나 원래는 동기적인 코드를 처리하는 용도로 사용했다.

그러나 ES8 async/await가 도입되면서 try/catch문을 비동기 코드에서 같이 작성할 수 있게 되었다.

.catch()를 통한 에러 처리

// then 핸들러 방식
function fetchResource(url) {
  fetch(url)
    .then(res => res.json()) /
    .then(data => {
      // data 처리
      console.log(data);
    })
    .catch(err => {
      // 에러 처리
      console.error(err);
    });
}

try/catch를 통한 에러처리

// async/await 방식
async function func() {
    try {
        const res = await fetch(url); 
        const data = await res.json();
        // data 처리
        console.log(data);
    } catch (err) {
        // 에러 처리
        console.error(err);
    }

}
func();

.then.catch체이닝을 사용하면 여러 단계의 비동기 작업을 처리할 때 코드가 중첩되어 가독성이 떨어진다.
반면

  • try/catch는 코드이 가독성이 높고, 유지 보수성이 좋다.
  • 동기적인 코드처럼 보이기에 흐름이 직관적이고 이해하기 쉽다.
  • try/catch는 하나의 블록으로 모든 에러를 처리할 수 있어서 중복된 에러 처리를 줄일 수 있다.

📌 fetch & axios(추가 예정)

fetch

fetch는 JS에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 해주는 메서드다.

// Syntax
fetch("URL", option)

URL : 요청할 서버 주소
option :

  • HTTP method (GET, POST, PUT, PATCH, DELETE)
  • headers
  • body
  • credentials
    등등 옵션은 요청의 설정을 정의하는 객체이다.
fetch("https://example.com/api", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "John", age: 30 }),
})
.then(// code..)
.catch(// code..)

axios


axios는 기존 fetch를 개선한 Promise기반의 HTTP 클라이언트
브라우저와 Node.js 환경에서 사용 가능하다.
API 요청을 더 간결하고 직관적이게 + 쉬운 에러 처리를 할 수 있다.

NOTE
여기서 말하는 동형이란 영어로 isomorphic으로

"같은 기능을 하지만 실행되는 환경이 다른"

정도로 이해하면 된다.
대표적으로 useEffectuseEffectLayout이 동형이다

사용법은 따로 링크로 대체 (예제)

참고자료

profile
Async FE 취업 준비중.. Await .. (취업완료 대기중) ..

0개의 댓글

관련 채용 정보