[React다루는기술] 16. 비동기 처리(Callback, Promise, Async/Await)


해당 포스팅에서는 javascript에서 비동기 처리와 비동기를 동기처럼 처리시에 사용되는 Callback Function, Promise, Async/Await에 대한 포스팅이다.


1. Callback Function

Callback 함수는 JavaScript에서 함수를 값으로 다루는 기능을 활용하여, 함수를 다른 함수의 매개변수로 전달하거나, 함수의 반환값으로 활용하는 함수입니다. 이것은 JavaScript의 함수형 프로그래밍 개념에 기반하고 있다.

Callback 함수는 주로 비동기 작업을 처리할 때 사용된 특정 요청 완료되면 특정 함수를 호출하도록 콜백 함수를 사용할 수 있다.

function fetchData(url, callback) {
  // 비동기 작업 수행 (예: 네트워크 요청)
  setTimeout(() => {
    const data = "데이터가 도착했습니다!";
    callback(data); // 콜백 함수 호출
  }, 1000);
}

function handleData(data) {
  console.log(data);
}

fetchData("https://example.com/api", handleData);


Callback 함수의 사용은 비동기 작업 및 이벤트 처리와 같은 상황에서 코드를 더 유연하게 만들어주며, 코드의 가독성과 유지보수성을 향상시킵니다. 그러나 콜백 지옥(callback hell)과 같은 문제가 발생할 수 있어, 이를 해결하기 위해 Promise나 async/await와 같은 패턴이 도입되기도 합니다.


ExCallback.js

아래는 콜백 지옥 형태를 갖고 있는 예제이다.

import { useState, useEffect } from "react";

const ExCallback = () => {
  const [cnt, setCnt] = useState(null);

  function increase(number, callback) {
    setTimeout(() => {
      const result = number + 10;

      if (callback) {
        callback(result);
      }
    }, 2000);
  }

  //useEffect에 두번째 인자를 []로 맨 처음 마운팅시에만 호출
  useEffect(() => {
    increase(0, (result) => {
      console.log(result);
      setCnt(result);

      increase(result, (result) => {
        console.log(result);
        setCnt(result);

        increase(result, (result) => {
          console.log(result);
          setCnt(result);

          increase(result, (result) => {
            console.log(result);
            setCnt(result);

            increase(result, (result) => {
              console.log(result);
              setCnt(result);
              console.log("콜백 처리 완료");
            });
          });
        });
      });
    });
  }, []);

  return <div>콜백 : {cnt}</div>;
};

export default ExCallback;
10
20
30
40
50
콜백 처리 완료



2. Promise

Promise를 사용하면 콜백 지옥(callback hell)을 피하고 코드 가독성이 좋아진다. Promise는 JavaScript에서 비동기적인 작업을 효과적으로 다룰 수 있도록 하는 객체입니다. 주로 네트워크 요청, 파일 읽기, 타이머 등과 같이 시간이 걸리는 작업을 처리할 때 사용됩니다. Promise는 세 가지 상태를 가진다. 대기(pending), 이행(fulfilled), 거부(rejected)


1. Promise의 생성

Promise는 생성자 함수를 통해 생성됩니다. 생성자 함수는 하나의 함수를 인자로 받으며, 이 함수는 두 개의 매개변수를 가지는데, 하나는 작업을 완료했을 때 호출할 resolve 함수이고, 다른 하나는 작업이 실패했을 때 호출할 reject 함수이다.

const myPromise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  // 성공 시 resolve 호출, 실패 시 reject 호출
});


2. Promise의 상태

  • 대기(pending): 초기 상태로, 작업의 완료 여부가 아직 결정되지 않은 상태
  • 이행(fulfilled): 작업이 성공적으로 완료된 상태입니다. resolve 함수가 호출된 상태
  • 거부(rejected): 작업이 실패한 상태입니다. reject 함수가 호출된 상태


3. Promise의 사용

Promise 객체는 then, catch, finally 메서드를 제공하여 비동기 작업의 성공, 실패, 완료 등을 처리할 수 있다.

myPromise
  .then((result) => {
    console.log("성공:", result);
  })
  .catch((error) => {
    console.error("실패:", error);
  })
  .finally(() => {
    console.log("완료");
  });


ExPromise.js

아래는 콜백지옥 예제코드를 Promise를 이용한 동일한 예제코드이다.

import { useState, useEffect } from "react";

const ExPromise = () => {
  const [cnt, setCnt] = useState(null);

  function increase(number) {
    const promise = new Promise((resolve, reject) => {
      //resolve: 성공, reject: 실패
      setTimeout(() => {
        const result = number + 10;

        if (result > 50) {
          return;
        }
        resolve(result);
      }, 2000);
    });

    return promise;
  }

  //useEffect에 두번째 인자를 []로 맨 처음 마운팅시에만 호출
  useEffect(() => {
    increase(0)
      .then((number) => {
        console.log(number);
        setCnt(number);
        return increase(number);
      })
      .then((number) => {
        console.log(number);
        setCnt(number);
        return increase(number);
      })
      .then((number) => {
        console.log(number);
        setCnt(number);
        return increase(number);
      })
      .then((number) => {
        console.log(number);
        setCnt(number);
        return increase(number);
      })
      .then((number) => {
        console.log(number);
        setCnt(number);
        console.log("Promise 작업 완료");
        return increase(number);
      })
      .catch((e) => {
        console.log(e);
      });
  }, []);

  return <div>콜백 : {cnt}</div>;
};

export default ExPromise;



3. Async / Await

async와 await는 ES8문법으로 JavaScript에서 비동기 코드를 더 간결하게 작성할 수 있도록 도와주는 키워드으로 Promise 기반의 비동기 코드를 더 구조적이고 동기적으로 작성할 수 있다.

1. async 함수

async 키워드를 함수 앞에 붙이면 해당 함수는 항상 Promise를 반환하다. async 함수 내에서 await 키워드를 사용하여 비동기 작업이 완료될 때까지 대기해서 동기처럼 처리한다.

async function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("응답");
    }, 1000);
  });
}


2. await 표현식

await는 async 함수 내에서만 사용 가능하며 await 다음에는 일반적으로 Promise를 반환하는 비동기 함수가 오며, 해당 함수가 완료될 때까지 await가 해당 값을 반환하다.

async function fetchDataAndDisplay() {
  const data = await fetchData();
  console.log(data);
}


3. async 함수의 반환값:

async 함수는 항상 Promise를 반환하므로 async 함수 내에서 return 키워드로 값을 반환하면, 해당 값은 resolve된 Promise의 결과 값이다.

async function fetchData() {
  return "데이터가 도착했습니다!";
}


ExPromiseAsyncAwait.js

아래는 Promise에 Async와 Await를 사용한 동기처럼 처리하는 예제 코드이다.

import { useState, useEffect } from "react";

const ExPromiseAsyncAwait = () => {
  const [cnt, setCnt] = useState(null);

  function increase(number) {
    const promise = new Promise((resolve, reject) => {
      //resolve: 성공, reject: 실패
      setTimeout(() => {
        const result = number + 10;

        if (result > 50) {
          return;
        }
        resolve(result);
      }, 2000);
    });

    return promise;
  }

  //비동기 처리한 함수에 async 키워드를 선언하고
  //해당 함수 내부에 Promise 앞부분에 await를 선언하면 동기 처리와 같이
  //기다려서 처리 된다.
  async function asyncCall() {
    try {
      let number = await increase(0);
      console.log(number);
      setCnt(number);

      number = await increase(number);
      console.log(number);
      setCnt(number);

      number = await increase(number);
      console.log(number);
      setCnt(number);

      number = await increase(number);
      console.log(number);
      setCnt(number);

      number = await increase(number);
      console.log(number);
      setCnt(number);
      console.log("Promise Async/Await 작업 완료");
    } catch (e) {
      console.log(e);
    }
  }

  //useEffect에 두번째 인자를 []로 맨 처음 마운팅시에만 호출
  useEffect(() => {
    asyncCall();
  }, []);

  return <div>콜백 : {cnt}</div>;
};

export default ExPromiseAsyncAwait;