개발/STUDY

Callback과 Promise 그리고 async/awail

송디 2024. 5. 23. 13:58

Callback 함수 

한 줄 정의 : 다른 함수에 인자로 전달되어 특정 작업이 완료된 후 호출되는 함수

 

■ 콜백함수의 작동 원리 

1. 함수 호출 : 비동기 작업을 수행하는 함수가 호출됨, 이 함수는 콜백 함수를 인자로 받음.

2. 비동기 작업 수행 : 함수는 비동기 작업(예: 타이머 설정, 네트워크 요청 등)을 시작함. 

3. 태스크 큐에 콜백 함수 추가 : 비동기 작업이 완료되면 콜백 함수가 태스크 큐에 추가됨.

4. 이벤트 루프 : 호출 스택이 비어 있으면 이벤트 루프가 태스크 큐에서 콜백 함수를 꺼내 호출 스택에 추가하고 실행함. 

 

예제 : 

let button = document.getElementById("button");

button.addEventListener("click", function () { // 콜백 함수
  console.log("clicked!"); 
});

 

왜 콜백함수? :

"비동기처리를 할 때 콜백함수를 사용해서 처리 할 수 있다." 로 주로 들어본 것 같다. setTimeout을 이용해 2초 후에 비동기 작업이 실행됬는지 확인하는 코드를 만들어보자. 

// 비동기 작업을 수행하는 함수
function doAsyncTask(callback) {
    console.log('비동기 작업 시작...');

    // 2초 후에 콜백 함수 호출
    setTimeout(() => {
        console.log('비동기 작업 완료!');
        callback(null, '작업 결과 데이터');
    }, 2000);
}

// 콜백 함수
function handleTaskCompletion(err, result) {
    if (err) {
        console.error('작업 중 오류 발생:', err);
        return;
    }
    console.log('콜백 함수 호출됨: ', result);
}

// 비동기 함수 호출
doAsyncTask(handleTaskCompletion);

 

위와 같이 만들 수 있을 것이다. 하지만 아래와 같은 '콜백 지옥' 과 같은 상황이 펼쳐질 수 있다. 

// 첫 번째 비동기 작업
function doFirstTask(callback) {
    console.log('첫 번째 작업 시작...');
    setTimeout(() => {
        console.log('첫 번째 작업 완료!');
        callback(null, '첫 번째 작업 결과');
    }, 1000);
}

// 두 번째 비동기 작업
function doSecondTask(data, callback) {
    console.log('두 번째 작업 시작...', data);
    setTimeout(() => {
        console.log('두 번째 작업 완료!');
        callback(null, '두 번째 작업 결과');
    }, 1000);
}

// 세 번째 비동기 작업
function doThirdTask(data, callback) {
    console.log('세 번째 작업 시작...', data);
    setTimeout(() => {
        console.log('세 번째 작업 완료!');
        callback(null, '세 번째 작업 결과');
    }, 1000);
}

// 콜백 지옥 예시
doFirstTask((err, result1) => {
    if (err) {
        console.error('첫 번째 작업 오류:', err);
        return;
    }
    doSecondTask(result1, (err, result2) => {
        if (err) {
            console.error('두 번째 작업 오류:', err);
            return;
        }
        doThirdTask(result2, (err, result3) => {
            if (err) {
                console.error('세 번째 작업 오류:', err);
                return;
            }
            console.log('모든 작업 완료:', result3);
        });
    });
});

 

Promise

이런 콜백 지옥을 보완하기 위해 필요한 것은 Promise 이다. 

Promise 자바스크립트 비동기 처리에 사용되는 객체로 일반적으로 데이터에 대한 응답을 받아올 때 사용되어 진다. 

데이터를 요청하고 받아오는 과정에서, 데이터를 아직 받지 않았는데, 화면에 표시하려고 하면 문제가 생긴다. 이를 방지하기 위해 Promise를 사용하여 응답을 받을 때 까지 기다린 후 실행이 되어진다. 

 

<script>
    function countdown(seconds) {
        return new Promise((function(resolve, reject){
             for(let i = seconds ;i >= 0 ; i--){
                 setTimeout(function(){
                     if(i===13) return reject(new Error("Oh my god"));
                     if(i > 0) console.log(i + '...');
                     else resolve(console.log("GO!"));
                 }, (seconds - i) * 1000)
             }           
        }))
    }
    countdown(3)
        .then(() =>console.log('데이터를 받은 후 실행'))
        
</script>

 

Promise의 상태는 총 3가지가 있는데, 아래와 같다.  

Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

 

Promise는 then, catch, finally와 같은 메서드를 통하여 적절한 작업을 수행할 수 있게 해준다 . 이런 메서드를 사용하여 여러 비동기 작업을 순차적으로 연결하는 것을 '프로미스 체이닝'이라고 한다. 

 

Async/Await

Promise를 기반으로 비동기 코드를 더 직관적이고 동기적인 방식으로 작성할 수 있게 해주는 문법이다. async/await를 사용하는 이유는 Promise는 여러개의 비동기 작업을 순차적으로 실행할 때 복잡해지고 문제가 생길 여지가 있기 때문이다.   'async' 키워드를 함수 앞에 붙이면 해당 함수는 항상 'Promise"를 반환하게 된다. 'await' 키워드는 'Promise'가 처리될 떄까지 기다렸다고 결과를 반환한다. 

async/await는 Promise와 달리 try~catch문을 통해 에러처리를 해줘야 한다. 

 

// Promise 체이닝 예시
fetchData1()
  .then(result1 => {
    return fetchData2(result1);
  })
  .then(result2 => {
    return fetchData3(result2);
  })
  .then(result3 => {
    console.log(result3);
  })
  .catch(error => {
    console.error(error);
  });

// async/await 예시
async function fetchAllData() {
  try {
    const result1 = await fetchData1();
    const result2 = await fetchData2(result1);
    const result3 = await fetchData3(result2);
    console.log(result3);
  } catch (error) {
    console.error(error);
  }
}

fetchAllData();
728x90

'개발 > STUDY' 카테고리의 다른 글

브라우저 동작 방법  (0) 2024.05.23
프로토타입  (0) 2024.05.23
렉시컬스코프와 클로저  (0) 2024.05.22
useState를 사용할 때는 왜 const를 쓰는가?  (0) 2024.05.22
var와 let 그리고 호이스팅  (0) 2024.05.22