[JS] 비동기프로그래밍 - Callback, Promise, Symbol
Tags: javascript, async, korean
Published: 2020-01-06 03:01:28

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

---

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

### [정기적인 실행시키기]

1. **setInterval**
2. **재귀적인 setTimeout**


```javascript
        let timerId = setTimeout(function tick() {
          alert('tick');
          timerId = setTimeout(tick, 2000);
        	// 현재 실행 끝난 후 2초간 딜레이 후 실행
        	// 딜레이 시간을 쓰지 않으면 바로 동기적으로 실행함
        }, 2000);
```


- setInterval 보다 유연하게 이용가능
ex) 서버에 요청이 너무 많아 딜레이를 늘려야 할 때


```javascript
            let delay = 5000;
            
            let timerId = setTimeout(function request() {
              // 요청 전송
            
              if(서버 과부하 때문에 요청이 실패한다면) {
                // 다음 실행까지 인터벌을 좀 늘리자
                delay *= 2;
              }
            
              timerId = setTimeout(request, delay);
            
            }, delay);
```


- **setInterval이 보장 못하는 실행간 딜레이를 보장**
    - setInterval은 실제로 기재한 시간보다 짧은 딜레이로서 동작
         → 딜레이 시간안에 함수의 동작시간이 포함되기 때문  

         → 함수 실행 종료 후에 딜레이가 걸리는게 아닌 단순 시간 을재서 호출을 하게 된다 
 
         → 극단적으로 함수 실행 시간이 길어질 경우 바로 실행될 수도 있다  

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



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


```javascript
    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 이용해서 브라우저 렌더 강제시키기]


```javascript
    <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(실패))**

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


```javascript
        // 콜백만 적용
        
        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로 추가하면 이름 충돌 걱정 하지않고 새 속성 계속 추가가능


```javascript
        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 symbols** 는 `Symbol.match` `Symbol.replace` `Symbol.search` `Symbol.iterator` `Symbol.split` 등등 이 있다.


```javascript
            'hyunwoo'.search(/woo/); // 4
            
            // 실행시 'hyunwoo'가 String 객체로 변환되고 내부의 search 메서드가
            // Symbol.search메서드에 접근
```

---

### 참고글

[자바스크립트는 어떻게 작동하는가: 이벤트 루프와 비동기 프로그래밍의 부상, async/await을 이용한 코딩 팁 다섯 가지](https://engineering.huiseoul.com/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94%EA%B0%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%98-%EB%B6%80%EC%83%81-async-await%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BD%94%EB%94%A9-%ED%8C%81-%EB%8B%A4%EC%84%AF-%EA%B0%80%EC%A7%80-df65ffb4e7e)

[자바스크립트 개발자라면 알아야 할 33가지 개념 #10 스케쥴링: setTimeout 과 setInterval](https://velog.io/@jakeseo_me/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-33%EA%B0%80%EC%A7%80-%EA%B0%9C%EB%85%90-10-%EC%8A%A4%EC%BC%80%EC%A5%B4%EB%A7%81-setTimeout-%EA%B3%BC-setInterval-y6juukjsey)

[Callback이 뭐죠?](https://medium.com/@nemo1275/callback%EC%9D%B4-%EB%AD%90%EC%A3%A0-5f3c0cc4efce)

[콜백](https://ko.wikipedia.org/wiki/%EC%BD%9C%EB%B0%B1)

[What is a Promise?](https://velog.io/@cadenzah/What-is-a-Promise)

[자바스크립트 프라미스: 소개 | Web Fundamentals | Google Developers](https://developers.google.com/web/fundamentals/primers/promises?hl=ko)

[[Javascript] Symbol 에 대해서](https://medium.com/@hyunwoojo/javascript-symbol-%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-6aa5903fb6f1)