DEV Community

Steve Sung
Steve Sung

Posted on

[JS] 비동기프로그래밍 - Callback, Promise, Symbol

비동기

  • 요청한 내용을 언젠간 응답해줄 것을 이라는 약속을 믿고 처리
  • 언젠간 응답이 올테니 다른 로직을 수행하고 있자

setTimeout, setInterval

  • setTimeout은 일정 시간 간격 이후에 함수가 한번 실행된다
  • setInterval은 일정 시간 간격으로 함수가 주기적으로 실행된다
    • clearInterval

[정기적인 실행시키기]

  1. setInterval
  2. 재귀적인 setTimeout
        let timerId = setTimeout(function tick() {
          alert('tick');
          timerId = setTimeout(tick, 2000);
            // 현재 실행 끝난 후 2초간 딜레이 후 실행
            // 딜레이 시간을 쓰지 않으면 바로 동기적으로 실행함
        }, 2000);
  • setInterval 보다 유연하게 이용가능 ex) 서버에 요청이 너무 많아 딜레이를 늘려야 할 때
            let delay = 5000;

            let timerId = setTimeout(function request() {
              // 요청 전송

              if(서버 과부하 때문에 요청이 실패한다면) {
                // 다음 실행까지 인터벌을 좀 늘리자
                delay *= 2;
              }

              timerId = setTimeout(request, delay);

            }, delay);
  • setInterval이 보장 못하는 실행간 딜레이를 보장

    • setInterval은 실제로 기재한 시간보다 짧은 딜레이로서 동작
      → 딜레이 시간안에 함수의 동작시간이 포함되기 때문

      → 함수 실행 종료 후에 딜레이가 걸리는게 아닌 단순 시간 을재서 호출을 하게 된다

      → 극단적으로 함수 실행 시간이 길어질 경우 바로 실행될 수도 있다

    • 재귀를 이용한 setTimeout은 함수실행 종료 후 딜레이를 적용하여 호출한다

[제로딜레이 setTimout으로 CPU 소비가 많은 작업을 splitting 하기]

    let i = 0;

    let start = Date.now();

    function count() {

      // 1부터 1000000000 세는 무거운 작업 
        // cpu 잠깐 멈출 듯함
      for (let j = 0; j < 1e9; j++) { 
        i++;
      }

      alert("Done in " + (Date.now() - start) + 'ms');
    }

    count();

    // setTimeout으로 스케줄링

    let i = 0;
    let start = Date.now();

    function count() {
      // 약간의 무거운 작업을 해봅시다. 1000000 단위로만 센다
      do {
        i++;
      } while (i % 1e6 != 0);

      if (i == 1e9) {
        alert("Done in " + (Date.now() - start) + 'ms');
      } else {
        setTimeout(count);
            // 호출을 스케쥴링합니다.
            // 1000000000에 도달하지 않았다면 다시 동기적으로 실행
      }
    }

    count();

    // 이 경우 작업을 setTimeout으로 나누든 안나누든 큰 차이는 없다

    // 차이를 만들기 위한 개선 코드

    let i = 0;
    let start = Date.now();

    function count() {
      // 스케줄링을 함수의 도입부로 옮김
      if ( i < 1e9 - 1e6) {
        setTimeout(count); // 함수 종료 후 실행됨
      }

      do {
        i++;
      } while (i % 1e6 != 0);

      if ( i == 1e9) {
        alert("Done in " + (Date.now() - start) + 'ms');
      }

    }

    count();

    // 작업전 스케줄링 후 작업 실행시 시간이 적게든다
    // 브라우저에 중첩된 타이머 딜레이를 최소화 가능하기 때문
    // 브라우저에서는, 중첩된 타이머를 얼마나 자주 동작할 수 있는지에 대한 제한이 있다.
    //  HTML5 표준은 "5개의 중첩된 타이머 이후에는 간격이 적어도 강제로 4ms 만큼 있을 것입니다." 
    // 라고 말한다.
    // 보통 실행시 1 1 1 1 9 15 24 ... 중첩되는 타이머가 늘어갈수록 점점 딜레이가 늘어간다
    // 스케줄링은 어느 부분에 하느냐에 따라서도 딜레이가 달라지게 된다

[setTimeout 이용해서 브라우저 렌더 강제시키기]

    <div id="progress"></div>

    <script>
    let i = 0;

    function count() {
      for (let j = 0; j < 1e6; j++) {
        i++;
        // 현재의 i 값을 progress div에 넣습니다.
        // innerHTML에 대해 더 알아봅시다.
        progress.innerHTML = i; // 다 i 작업 끝난후에 html에 반영된다 
      }
    }

    count();
    </script>

    // setTimeout 적용

    <div id="progress"></div>

    <script>
      let i = 0;

      function count() {

        // do a piece of the heavy job (*)
        do {
          i++;
          progress.innerHTML = i;
        } while (i % 1e3 != 0);

        if (i < 1e9) {
          setTimeout(count); // 순차적으로 html에 계속 반영
        }

      }

      count();
    </script>

Callback

  • 프로그래밍에서 콜백(callback)은 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다
  • 콜백함수
    • called at the back on the other function
    • 다른함수의 인자로서 이용되는 함수
    • 어떤 이벤트에 의해 호출되는 함수
  • 콜백지옥 : 비동기 처리 로직을 이용시에 콜백 함수를 연속으로 사용할 때 일단 코드가독성도 떨어지고 안전장치도 없기때문에 정확하게 호출을 확신할수가 없게 되어 버그관리에 취약하게 된다.

Promise

[개요]

  • 비동기 방식을 다루는 최선의 방법을 논하고 있었다.
  • 여러 실험이 벌어지기도 했지만 Node의 표준인 오류-우선 콜백 패턴을 주로 사용하게 되었다
  • Dojo toolkit, JQuery 등이 Promise방식을 사용하면서 Promise를 대세로 만들었다
  • fetch, async 등 중요한 api 들도 새로운 Promise 표준을 기반으로 만들어 졌다

[작동방식]

  • 프로미스에서도 콜백이 사용되긴 하지만 예측가능한 패턴에서만 이용한다
  • 기본개념은 프로미스 기반 비동기적 함수를 호출하면 그함수는 Promise 인스턴스를 반환하게되고 성공 or 실패 두가지만 확인한다.
  • 3가지 state 존재 (pending(대기), fulfilled(이행), Rejected(실패))

    비동기 처리를 다른곳에서 하고싶으면 프로미스 객체를 넘기기만 하면된다.(마치 호출기를 다른사람에게 주듯)

        // 콜백만 적용

        function getData(callbackFunc) { 
        $.get('url 주소/products/1', function (response) 
                    { callbackFunc(response);
                    // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌 
        }); }

        getData(function (tableData) { 
                    console.log(tableData); // $.get()의 response 값이 tableData에 전달됨 
                    });

        // 프로미스 적용

        function getData(callback) {

        // new Promise() 추가

         return new Promise(function (resolve, reject) { // 성공 아니면 실패

        $.get('url 주소/products/1', function (response) {
            // 데이터를 받으면 resolve() 호출 
        resolve(response); }); }); }

        // getData()의 실행이 끝나면 호출되는 then()
        getData().then(function (tableData) { // resolve()의 결과 값이 여기로 전달됨
                console.log(tableData); // $.get()의 reponse 값이 tableData에 전달됨
         });
  • resolve() 실행시 프로미스의 state가 fullfilled가 되고 then()을 통해 처리 결과 값을 받을 수 있다.

  • reject() 실행시 프로미스의 state가 rejected가 되고 catch()를 통해 에러 값을 받을수 있다

  • fetch 실행시 성공 실패 여부에 따라 Promise 실행하고 그에따라 then 이나 catch로 실행된다


Symbol

  • 내부 key 값 걱정없이 추가 프로퍼티를 만들수 있는 고유의 값 만들 때 이용

  • 객체 프로퍼티 키 일 수도 있다

  • Symbol로 추가하면 이름 충돌 걱정 하지않고 새 속성 계속 추가가능

        var includes = Symbol('즐거운 자바스크립트');
        // Symbol은 객체는 아니다

        Array.prototype[includes] = function () {
          return console.log('its Symbol');
        }

        var arr = [1, 2, 3];
        arr.includes(1); // true
        arr['includes'](1); // true
        // arr 기본 메서드 동작
        arr[includes](); // its Symbol // Symbol('즐거운 자바스크립트'); 가 Key로 들어감
        // 충돌하지 않고 Symbol객체로 만든 key 동작

갑자기 prototype에 같은 이름을 가지는 함수가 새 ES 버전에 생기는 문제 피할 수 있다 → name 충돌방지

well-known Symbol 통해 core method 접근 가능
- 자바스크립트는 자동으로 몇몇 Symbol 변수를 생성하고 global Symbol 객체에 할당한다
- “well-known” global symbolsSymbol.match Symbol.replace Symbol.search Symbol.iterator Symbol.split 등등 이 있다.

            'hyunwoo'.search(/woo/); // 4

            // 실행시 'hyunwoo'가 String 객체로 변환되고 내부의 search 메서드가
            // Symbol.search메서드에 접근

참고글

자바스크립트는 어떻게 작동하는가: 이벤트 루프와 비동기 프로그래밍의 부상, async/await을 이용한 코딩 팁 다섯 가지

자바스크립트 개발자라면 알아야 할 33가지 개념 #10 스케쥴링: setTimeout 과 setInterval

Callback이 뭐죠?

콜백

What is a Promise?

자바스크립트 프라미스: 소개 | Web Fundamentals | Google Developers

[Javascript] Symbol 에 대해서

Top comments (0)