ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 콜백 함수와 Promise
    Javascript 2024. 2. 6. 15:16

    콜백 함수(Callback Function)

    function repeatFunc(n, func) {
      for (let i = 0; i < n; i++) {
        func(i);
      }
    }
    
    function odd(i) {
      if (i % 2) console.log(i);
    }
    
    function even(i) {
      if (i % 2 == 0) console.log(i);
    }
    
    repeatFunc(5, odd)	// 1, 3
    repeatFunc(5, even)	// 0, 2, 4

    콜백함수 예시

    • 매개변수를 통해 다른 함수의 내부로 전달되는 함수
    • 매개변수를 통해 함수의 외부에서 전달받은 콜백 함수를 고차 함수라고 함

     

     

    콜백 함수의 사용

    1. 함수의 구조적 설계

    • 콜백 함수를 사용하여 로직 처리 부분과 나머지 부분을 구분할 수 있으며, 이를 통해 함수의 역할을 나누고 재사용하는 등 구조적인 함수 설계가 가능해짐
    function benchPress(day) {
      console.log('벤치프레스');
    }
    
    function deadLift() {
      console.log('데드리프트');
    }
    
    function squat() {
      console.log('스쿼트');
    }
    
    function today(day) {
      console.log(`${day}의 운동 : `);
    }
    
    function todayWorkout(day) {
      if (day === '월요일') {
        today(day);
        benchPress();
      } else if (day === '화요일') {
        today(day);
        deadLift();
      } else if (day === '수요일') {
        today(day);
        squat();
      }
    
      // 목요일... 금요일... 토요일...
    }
    
    todayWorkout('월요일');
    todayWorkout('화요일');

    목요일, 금요일, 토요일,.. 다른 요일의 루틴을 추가하려면 else if문을 추가해야함.

    화요일의 루틴을 변경하고 싶으면 todayWorkout함수의 else if문에 있는 squat()함수를 직접 바꿔야함.

     

    function benchPress(day) {
      console.log('벤치프레스');
    }
    
    function deadLift() {
      console.log('데드리프트');
    }
    
    function squat() {
      console.log('스쿼트');
    }
    
    function today(day) {
      console.log(`${day}의 운동 : `);
    }
    
    function todayWorkout(day, routine) {
      today(day);
      routine(day);
    }
    
    todayWorkout('월요일', benchPress);
    todayWorkout('화요일', deadLift);

    루틴을 추가하려면 todayWorkout함수에 요일과 루틴 함수를 입력하여 호출하면 됨.

    화요일의 루틴을 변경하고 싶으면 todayWorkout의 두 번째 인자에 다른 함수를 입력.

     

     

    2. 비동기 처리

    • 자바스크립트는 코드를 위에서부터 순차적으로 실행하지만, 실제로 실행 시켰을 때 순서가 보장되지 않는 경우가 있음.
    • 함수의 비동기 처리 결과에 대한 후속 처리를 수행하기 위한 용도로 콜백 함수가 사용된다.
    • 대표적인 비동기 처리
      • setTimeout(타이머 함수) : 두 번째 인자로 받은 만큼의 ms가 지난 이후 첫 번째 인자로 받은 콜백 함수를 실행
      • fetch(API 호출 함수) : api를 호출의 결과를 콜백 함수로 받아 then, catch메서드로 후속 처리를 수행
    // 1초 후에 콜백 함수가 실행됨
    setTimeout(function () {
      console.log('Time is up!');
    }, 1000);
    
    console.log('내가 setTimeout 함수 이후에 출력될까?');
    console.log('아닌데ㅋ');

    타이머 함수 예시

    타이머 함수 출력 결과

    // fetch메서드를 이용한 API호출
    fetch('https://url')
      .then(function (response) {
        // fetch 메서드가 성공하면 콜백 함수에 response인자를 받음
        return response.json();
      })
      .then(function (data) {
        // 위의 then에서 리턴받은 json 메서드가 성공하면 콜백 함수로 data인자를 받음
        console.log(data);
      })
      .catch(function (error) {
      	// fetch, then단계 어디서든 에러가 발생하면 catch구문에 도달하여 콜백 함수로 error인자를 받음
      	console.log(error);
      });

    fetch 함수 예시

     

    3. 이벤트 리스너

    • 클릭과 같은 html 이벤트가 발생한 이후 콜백 함수를 실행
    let button = document.getElementById("button");
    button.addEventListener("click", function () { 
      console.log("클릭"); // 콜백 함수에서 출력문을 실행
    });

    addEventListener 메서드 예시

     

    4. 고차함수에서의 사용

    • forEach(), map(), find() 와 같은 배열 고차함수에서, 배열의 각 요소에 대해 콜백 함수를 실행하고 결과를 반환
    const nums = [1, 2, 3, 4];
    let sum = 0;
    
    // forEach메서드
    nums.forEach((num) => {
      sum += num;	// 배열을 순회하며 콜백 함수를 수행
    });
    console.log(sum);   // 10
    
    // map메서드
    const numMap = nums.map((num) => {
      return num + 2;	// 배열을 순회하며, 최종적으로 각 요소마다 return문을 수행된 결과 배열을 반환
    });
    
    console.log(numMap);  // (4) [3, 4, 5, 6]

    forEach(), map()메서드 예시

     

     

    Promise 객체의 등장 배경

    • 비동기 함수는 비동기 처리의 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다. 결국 비동기 함수의 내부에서 후속 처리를 수행할 수 밖에 없다.
    // 쇼핑몰의 판매탭에서 상품의 id를 가져오고
    get('https://.../shopping/sales/1', (goodsId) => {
      // 상품의 id로 상품의 정보를 찾고
      get(`https://.../shopping/goods/${goodsId}`, (goodsInfo) => {
        // 상품 정보에서 또 다른 무엇인가를 찾고..
        get(`https://.../shopping/someWhere/${goodsInfo}`, (someId) => {
          // 다른 url로 이동해서 또..
          get(`https://.../shopping/anyWhere/${someId}`, (something) => {
            console.log(something);
          });
        });
      });
    });

    콜백 헬 예시

    • VanillaJS로 API 요청 코드를 작성하면 훨씬 복잡하지만, 간단하게 get('url주소', function(인자) {이후 수행될 콜백 로직})이라는 함수를 만들었다고 치자.
      위의 예시와 같이 요청이 성공하면 응답을 가져오고, 그 응답을 이용해서 또 새로운 요청을 하고... 가 반복되다보면 들여쓰기가 반복되어 가독성이 떨어지며, 실수를 유발하는 원인이 된다. => 콜백 헬
    fetch('https://url')
      .then(function (response) {
        // fetch 메서드가 성공하면 콜백 함수에 response인자를 받음
        return response.json();
      })
      .then(function (data) {
        // 위의 then에서 리턴받은 json 메서드가 성공하면 콜백 함수로 data인자를 받음
        console.log(data);
      })
      .catch(function (error) {
        // fetch, then단계 어디서든 에러가 발생하면 catch구문에 도달하여 콜백 함수로 error인자를 받음
        console.log(error);
      });

    Promise Chaining을 이용한 코드

    • 이를 해결하기 위해 ES6버전에서 Promise객체가 등장하였고, Promise의 후속 처리 메서드인 then, catch, finally를 이용하여 깔끔한 코드를 작성할 수 있다.

     

     

    Promise 객체

    프로미스?

    1. Promise의 상태

    Pending(대기)

    const promise = new Promise(function (reslove, reject) {});
    console.log(promise);   // Promise {[[PromiseState]]: 'pending', [[PromiseResult]]: undefined, ...
    • new Promise() 메서드로 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject
    • 처음 호출 시 대기 상태(Pending)가 되며, 결과 값은 undifined.

    Fulfilled(이행)

    const promise = new Promise(function (reslove, reject) {
      const data = 'resolved data';
      reslove(data);
    });
    
    promise
      .then(function (data) {
        console.log(data);  // resolved data
      });
    • 콜백 함수의 인자인 resolve함수를 실행하면 이행(Fulfilled) 상태가 되며, reslove함수의 인자가 결과 값으로 할당됨
    • 이행 상태가 되면 then() 메서드를 이용하여 처리 결과를 콜백 함수로 받을 수 있다.

    Rejected(실패)

    const promise = new Promise(function (reslove, reject) {
      reject(new Error('요청 실패ㅠㅠ'));
    });
    
    promise
      .then(function (data) {
        console.log(data);
      })
      .catch(function (error) {
        console.log(error);   // Error: 요청 실패ㅠㅠ ...
      });
    • 콜백 함수의 인자인 rejected함수를 실행하면 실패(Rejected) 상태가 되며, reject함수의 인자가 결과 값으로 할당됨
    • 실패 상태가 되면 catch() 메서드를 이용하여 처리 결과를 콜백 함수로 받을 수 있다.

     

    2. Promise Chaining

    콜백 헬 해결!

    fetch(
      'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1'
    )
      .then(function (response) {
        return response.json();
      })
      .then(function (data) {
        console.log(data[0].url);
      })
      .catch(function (error) {
        console.log(error);
      })
      .finally(function () {
        console.log('난 성공하든 실패하든 일단 출력된단다.');
      });
      
      // 출력 결과
      // https://cdn2.thecatapi.com/images/FUJOW3SIi.jpg
      // 난 성공하든 실패하든 일단 출력된단다.
    • Promise객체의 등장 배경에서 살펴본 바와 같이 then(), catcth(), finally() 메서드를 이용하여 콜백 헬을 해결할 수 있다.
    • 이처럼 연속적인 호출이 가능한 이유then(), catcth(), finally() 메서드가 모두 Promise 객체를 반환하기 때문이다.

     

    3. Promise.all()

    여러 개의 비동기 처리를 병렬적으로 처리하는 메서드

    • Promise.all() 메서드는 인자를 Promise객체를 요소로 갖는 배열로 받아서 비동기 작업을 병렬적으로 처리한다.
    • 인자로 전달받은 배열의 모든 Promise가 fulfilled상태가 되면 종료한다.
      따라서 모든 처리에 걸리는 시간은 가장 늦게 fulfilled상태가 되는 작업보다 조금 길다.
    • 배열의 Promise중 하나라도 rejected 상태가 되면 나머지가 fulfilled상태가 되는 것을 기다리지 않고 즉시 종료한다.

    Cat API를 활용한 예제

    예제를 위해 https://thecatapi.com/의 API를 사용하였습니다.
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <h1>Cat Image</h1>
        <div id="imgBox"></div>
      </body>
      <script>
        function insertImg(url) {
          const imgBox = document.getElementById('imgBox');
          const img = new Image();
          img.src = url;
          img.width = 300;
          img.height = 200;
          imgBox.appendChild(img);
        }
    
        const getCat = function () {
          return fetch(
            'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1'
          )
            .then(function (response) {
              return response.json();
            })
            .then(function (data) {
              const imgSrc = data[0].url;
              insertImg(imgSrc);
            });
        };
    
        // Promise Chaing을 이용하여 비동기 요청이 순차적으로 수행됨
        getCat()
          .then(function () {
            return getCat();
          })
          .then(function () {
            return getCat();
          });
      </script>
    </html>

    • Promise Chaining을 이용하여 작성한 코드는 개발자 도구의 네트워크창을 통해 확인해보았을 때, 이미지를 받아오고 그리는 작업이 순차적으로 수행된 것을 볼 수 있었다.

     

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <h1>Cat Image</h1>
        <div id="imgBox"></div>
      </body>
      <script>
        function insertImg(url) {
          const imgBox = document.getElementById('imgBox');
          const img = new Image();
          img.src = url;
          img.width = 300;
          img.height = 200;
          imgBox.appendChild(img);
        }
    
        const getCat = function () {
          return fetch(
            'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1'
          )
            .then(function (response) {
              return response.json();
            })
            .then(function (data) {
              const imgSrc = data[0].url;
              console.log(imgSrc);
              return imgSrc;
            });
        };
    	
        // Promise.all을 사용하여 비동기 요청이 병렬적으로 수행됨
        Promise.all([getCat(), getCat(), getCat()]).then((dataList) => {
          dataList.forEach((imgSrc) => {
            insertImg(imgSrc);
          });
        });
      </script>
    </html>

     

    • Promise.all() 메서드를 사용했을 때에는 이미지를 받아오고 그리는 작업이 병렬적으로 수행되었다.

    'Javascript' 카테고리의 다른 글

    [Javascript] this와 화살표 함수  (0) 2024.02.25
    [Javascript] Javascript의 특징  (0) 2024.01.25
Designed by Tistory.