본문 바로가기
프론트엔드

비동기 처리

by 박수련 2024. 2. 23.

비동기 코드의 실행 

자바스크립트는 싱글 스레드로 실행되기 때문에 동기적으로 동작한다. 

따라서 자바스크립트에서 비동기 코드를 처리하기 위해서는 자바스크립트 외부의 도움을 받게 된다.

 

밑의 그림처럼 자바스크립트 코드가 실행되면서 call stack에 코드가 쌓이게 된다. 

이때 비동기 코드는 call stack에 있다가 바로 WebAPI에 보내지게 되고 이곳에서 비동기 코드가 처리된다.

처리가 완료되면 callback queue에 대기하며 call stack이 완전히 비었을 때, event loop에 의해 call stack으로 넘어와 실행된다. 

즉, 모든 동기 코드가 실행된 후에야 비동기 코드들이 실행된다. 

https://www.webdevolution.com/blog/Javascript-Event-Loop-Explained

 

비동기 처리

 

비동기 요청이 여러 개일 때, 하나의 요청이 다른 요청의 결과에 의존한다면?

 

작업 1과 작업2가 있다.

작업 1은 데이터를 불러오는 작업이고, 작업 2는 해당 데이터를 출력하는 작업이다.

작업 1이 모두 완료가 되었을 때 작업 2를 처리하고 싶지만

작업 1에 비동기 처리를 했기 때문에 작업 1이 끝나지 않아도 작업 2가 실행된다.

이러한 문제점을 해결하기 위해 다음과 같은 방법들이 있다.

 

비동기 처리 방법

1. callback 함수

callback 함수는 특정 함수에 매개변수로 전달된 함수로 나중에 호출할 함수이다.

비동기 처리 함수 내부에 callback함수를 넣어 

 

callback을 많이 사용하게 되면 함수 안에 함수가 있고, 또 함수가 있는 callback hell이 발생하여 가독성이 좋지 않다.

document.getElementById('getDataBtn').addEventListener('click', function() {
    $.get('https://api.example.com/data1', function(data1) {
        console.log('첫 번째 데이터:', data1);
       
        $.get(`https://api.example.com/data2/${data1.id}`, function(data2) {
            console.log('두 번째 데이터:', data2);
            
            $.get(`https://api.example.com/data3/${data2.userId}`, function(data3) {
                console.log('세 번째 데이터:', data3);
                
                document.getElementById('result').innerHTML = `최종 결과: ${data3.details}`;
            });
        });
    });
});

 

2. Promise

callback 함수의 가독성 문제를 보완하고자 ES6에서는 비동기 처리를 위한 프로미스를 도입했다.

 

Promise란 비동기 작업이 종료된 후,

  1. 실행이 잘 성공했는지
  2. 혹은 실패했는지
  3. 그럼 성공 or 실패의 결과 값이 무엇인지

위 세가지 내용을 비동기 작업이 종료된 후에 반환해주는 객체이다.

 

Promise 생성자 함수는 resolve와 reject 함수를 인자로 전달받는다.

비동기 요청 시 state: pending, result: undefined 에서 

비동기처리가 성공하면 state: fulfilled, result: value로 되고 resolve를 호출한다. 

비동기처리가 실패하면 state: rejected, result: error로 되어 reject함수를 호출한다. 

 

Promise의 결과값은 내부 객체이기 때문에 then, catch로만 접근이 가능하다고 한다.

const fetchData = new Promise((resolve, reject) => {
   const success = true;
   
   if (success) {
     resolve('성공');
   } else {
     reject('실패');
   }
 });

 fetchData
   .then((result) => {
     console.log(result);
   })
   .catch((error) => {
     console.log(error);
   });

 

Promise 생성자는 프로미스를 지원하지 않은 함수를 감쌀 때 사용되며, fetch( )는 Promise를 지원하기 때문에 따로 생성자를 만들 필요가 없다!

 

Promise.all([ ])

여러 프로미스가 담긴 배열을 인자로 받아 하나의 프로미스를 반환한다.

모든 프로미스가 fulfilled되어야 fulfilled 상태를 반환한다.

프로미스가 순서대로 처리된다.

프로미스가 하나라도 실패하면 가장 먼저 실패한 프로미스가 reject한 에러를 reject하는 새로운 프로미스를 반환한다.

 

Promise.race([ ])

인자로 받은 모든 프로미스를 병렬 처리하는 것이 아니라 가장 먼저 처리된 프로미스가 resolve한 처리 결과를 resolve하는 새로운 promise를 반환한다.

 

3. async, await

Async, await는 promise와 다르게 promise 반환값을 변수에 담아 동기적 코드처럼 보이게 할 수 있다.

async가 걸려있는 함수는 비동기로 작동한다.

이때 함수 내의 특정 작업은 동기식으로 작동되어야하는 경우가 있기 때문에 이를 위해 await를 사용한다.

함수에서 await를 만난다면 해당 작업이 완료될 때까지 함수의 실행이 일시 중단된다.

 

여러 작업 await가 걸려있는 비동기 작업을 동시에 실행시키고 싶다면 Promise.all( )을 사용해야한다.

await 또한 Promsie 객체를 반환하기 때문에, Promise 객체가 완료될 때까지 코드 실행을 일시 중지하므로 try catch 구문을 통해 에러 처리를 할 수 있다.

 

async function makeRequest() {
  try {
    const response1 = await fetch(
      'https://jsonplaceholder.typicode.com/todos/1'
    );
    const jsonResponse1 = await response1.json();
    console.log('jsonResponse1', jsonResponse1);

    const response2 = await fetch(
      'https://jsonplaceholder.typicode.com/todos/2'
    );
    const jsonResponse2 = await response2.json();
    console.log('jsonResponse2', jsonResponse2);
  } catch (error) {
    console.log('error', error);
  } finally {
    console.log('---모든 작업 끝---');
  }
}

 

참고

https://poiemaweb.com/es6-promise

https://adrianmejia.com/promises-tutorial-concurrency-in-javascript-node/ (읽어보기)

https://trustmitt.tistory.com/85 (필요성)

https://velog.io/@coin46/%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EC%BD%9C%EB%B0%B1-Promise-asyncawait#callback-%ED%95%A8%EC%88%98

'프론트엔드' 카테고리의 다른 글

[React] React Hook Form의 useForm 사용기  (0) 2024.04.11