ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 비동기 - #1. 비동기 개념과 Callback Function
    Front-End(Web)/Javascript 2021. 1. 30. 07:37
    반응형

    Javascript 메서드를 사용하면 인자로 받는 함수를 '콜백함수' 라고 명명하는 것을 보았을 것이다.

    React 작업을 하면서, fetch() 함수의 .then 혹은 async/await 등 '비동기 프로그래밍'을 자주 적용했다.

     

    위 내용들 모두, 해당 코드를 진행함과 동시에 다음 코드로 넘어가기 위한 '비동기'의 목적이라 막연하게 알고 있었다.

    (컴퓨터 언어 특성상 코드는 위에서 아래로 순차적으로 실행되기 때문! - 싱글 스레드)

     

    그렇지만, 그래서 비동기가 뭔데? 라는 질문에 저 이상으로 답을 할 수 없었고,

    이번 기회에 Javascript의 비동기 관련된 동작원리를 시리즈로 정리하면서 공부하고자 포스팅을 시작하였다.


    📒 비동기(Asynchronous) vs 동기(Synchronous)

    출처: https://codeameba.netlify.app/blog/why-using-async

     

    - 동기(Synchronous)

    위에서 언급했듯, Javascript도 기본적으로 순차적으로 처리되는 동기식 언어이다.

    Single-Threaded 혹은 Blocking 특성을 가지고 있다고도 얘기할 수 있다.

     

    동기는 말 그대로 코드가 순차적으로 실행되며, 현재 프로세스가 완료되어야 다음으로 넘어간다.

    function func1() {
      console.log('func1');
      func2();
    }
    
    function func2() {
      console.log('func2');
      func3();
    }
    
    function func3() {
      console.log('func3');
    }
    
    func1();
    
    // func1
    // func2
    // func3

    위 예시는 대표적인 동기 처리로, func1() 함수가 실행되면 내부의 console -> func() 순서로 진행되는 것을 알 수 있다.

     

     

    * 동기 처리의 원리

     

    자바스크립트의 동기 원리를 설명하는 좋은 글이 있어, 간략하게 정리해보았다.

    콘솔이 순차적으로 실행되는 원리를 구글의 Javascript V8엔진(Node, Chrome 브라우저) 기반으로 정리했다.

    출처 : https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

    우선, 그림의 Memory Heap과 Call Stack은 자바스크립트 엔진의 주요 구성요소이다.

    • Memory Heap: 변수와 객체의 메모리 할당을 담당
    • Call Stack: 함수가 호출되면 쌓이는 곳이다. 실행(반환)은 마지막에 쌓인 함수부터 나간다.
    function multiply(x, y) {
        return x * y;
    }
    function printSquare(x) {
        var s = multiply(x, x);
        console.log(s);
    }
    printSquare(5);

    위 코드와 그림을 보면 이해할 수 있을 것이다! printSquare()가 호출과 동시 Stack에 쌓인다.

    이후, 내부의 multiply()와 console이 차례로 Push - Pop 되며 실행되고, 최종적으로 printSquare()가 실행된다.

     

     

    - 비동기(Asynchronous)

    자바스크립트는 위에서 말했듯 순차적으로 진행되지만, 이것이 비효율적인 경우가 있을 것이다.

    가령, 웹페이지가 로딩되거나 데이터를 불러올 때 동기적으로 처리한다면 그만큼 렌더링이 지연될 것이다.

    과연... 한국인들이 이 사태를 관망할 수 있을까?!?! 나부터 아니라고 본다 🙄🙄

     

    이렇게 오래 걸리는 로직이 끝날 때까지 코드실행을 멈추지 않고, 다음 코드를 우선 실행하는 것을 '비동기 프로그래밍' 이라고 정의할 수 있겠다.

     

    function func1() {
      console.log('func1');
      func2();
    }
    
    function func2() {
      setTimeout(function() {
        console.log('func2');
      }, 0);
    
      func3();
    }
    
    function func3() {
      console.log('func3');
    }
    
    func1();
    
    // func1
    // func3
    // func2

     

    위는, 자바스크립트의 대표적인 비동기 메서드인 setTimeout() 을 통해 func2() 함수를 비동기처리 한 것이다.

    (setTimeout첫 번째 인자로 콜백함수(비동기)를, 두 번째 인자로 인터벌을 받는다. 0초여도 비동기 적용!)

     

    1. func1(), func2() 순으로 Call Stack에 누적된다.
    2. func2()의 비동기 함수(setTimeout)가 누적되면 Web API(Background)로 이동한다.(DOM, 비동기, Ajax 등)
    3. Web API(Background)의 비동기 함수가 실행되면, 내부 콜백함수가 Event Queue로 이동한다.
    4. 이후, Event Loop가 Call Stack을 확인 -> 비어있다면 Call Stack으로 Event Queue의 콜백함수를 push한다.
    5. Call Stack으로 이동한 콜백함수가 실행되며, Call Stack에서 최종적으로 나가게 되는 것이다.(console.log('func2'))

    * Heap : 구조화되지 않은 더미(dummy) 메모리 영역. 동적으로 생성된 객체들이 이곳에 할당(Memory Heap)

    * Queue : JS 런타임 환경에서 처리해야 할 작업(Task)들의 임시 저장소. Call Stack이 비면 Queue 대기순으로 Push

    * Event Loop : JS 싱글 스레드의 메인 스레드에 해당한다.

     


    📒 비동기 메서드: setTimeout(), setInterval()

     

    - setTimeout()

    위에서 잠깐 다뤘듯, setTimeout()은 일정 인터벌을 두고 내부의 콜백함수를 비동기로 실행하는 메서드이다.

    setTimeout(callback function, 3000);

    첫 번째 인자는 비동기로 실행할 콜백함수를 받는다.

    두 번째 인자는 비동기만큼 지연시킬 시간을 설정한다. 단위는 ms이다. (고로 위 예시는 3초가 지연될 것이다.)

     

    * 비동기 삭제하고자 할 시, clearTimeout() 메서드 활용. (setTimeout 로직을 담은 변수를 인자로 받는다.)

     

    - setInterval()

    setTimeout이 시간차로 실행된다면, setInterval은 말그대로 인터벌 간격마다 콜백함수가 실행되는 메서드다.

    setInterval(callback function, 3000);

    * 인터벌을 삭제하고자 할 시, clearInterval() 메서드 활용. (setInterval 로직을 담은 변수를 인자로 받는다.)

     


    📒 Callback Function

    콜백함수에 대해 좀 더 자세히 알아보도록 하자! 콜백은 무조건 비동기로 실행되는 함수는 아니다.

    콜백함수의 기본개념은 다른 함수의 인자로 사용되거나 이벤트에 의해 호출되는 함수를 일컫는다.

     

    자바스크립트에선 함수도 객체의 일종이다. 그렇기에 함수의 인자로 함수를 받을 수 있고, 이를 반환할 수도 있다.

    이러한 함수간의 연계를 통해, 각각 선언된 함수들(Hoisting)의 스케줄링이 가능해진다. (비동기의 동기화)

     

    // Synchronous Callback
    const printSync = (print) => {
      print();
    }
    printImmediately(() => console.log('sync!'));		// sync!
    
    // Asynchronous Callback
    const printAsync = (print, interval) => {
      setTimeout(print, interval);
    }
    printImmediately(() => console.log('async!'), 3000);	// ........ async!

     

     

    * Callback Hell(콜백지옥) 😈

     

    현재는 Promise, async/await 등으로 대체가 되었지만, 이전 자바스크립트는 콜백함수로 비동기 스케줄링을 작성했다.

    문제는, 콜백함수를 연속해서 사용하면 가독성도 안좋아질 뿐더러 비동기 로직의 에러 발생 가능성이 높아진다.

    //Callback Hell : 가독성 낮음(코드일기 및 로직이해 어려움)
    class UserStorage {
        loginUser(id, pw, onSuccess, onError) {             //API1
            setTimeout(() => {
                if (
                    (id === 'ellie' && pw === 'dream') ||
                    (id === 'coder' && pw === 'academy') 
                ) {
                    onSuccess(id);
                } else {
                    onError(new Error('not found'));
                }
            }, 2000);
        }
    
        getRoles(user, onSuccess, onError) {                //API2
            setTimeout(() => {
                if (user === 'ellie') {
                    onSuccess({name: 'ellie', role: 'admin'});
                } else {
                    onError(new Error('no access'));
                }
            }, 1000);
        }
    }
    
    const userStorage = new UserStorage();
    const id = prompt('enter your id');
    const pw = prompt('enter your pw')
    userStorage.loginUser(
        id, 
        pw, 
        user => {
            userStorage.getRoles(
                user, 
                userWithRole => {
                    alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role.`)
                },
                error => {console.log(error)},);
        }, 
        error => {console.log(error)});

    UserStorage라는 클래스와, 안에는 로그인 로직을 구현하는 메서드들을 선언했다.

    특히, onSuccess, onError 인자는 성공/실패시 실행할 함수를 받는데, 여기서 콜백, 콜백이 선언되면서 지옥이 펼쳐진다.

     

    콜백지옥을 해결하기 위해, 콜백함수들을 각각의 함수로 선언하는 코딩 패턴을 권장한다.

    나아가서는, 이를 개선하기 위해 Promise(ES7) 와 Async/Await(ES8) 이 등장하게 된 것이다. (이는 이후 블로깅)


    📒 Ajax (Asynchronous Javascript and XML)

    비동기 처리의 대표적인 다른 예시로, 자바스크립트 서버 통신방법인 Ajax가 있다.

    비동기적 자바스크립트와 XML의 약자로, 풀이 그대로 자바스크립트가 비동기적으로 데이터 통신을 하는 형태이다.

     

    Ajax 모델은 jQuery와 많이 조합되었는데,

    가장 큰 장점으론 비동기 서버통신을 통한 최적화와 데이터가 포함된 일부 페이지만 로드한다는 점이 있었다.

     

    Ajax에 대해서는 추후 별도 포스팅을 통해 상세히 다루기로 하고,

    우선 자바스크립트가 서버 데이터를 비동기적으로 받아온다는 것만 인지하도록 하자!!

    Traditional LifeCycle
    Ajax LifeCycle

    * XML(eXtensible Markup Language) : Ajax 통신시 사용되던 언어로, HTML과 유사한 마크업 언어이다.

        -> 최근에 들어서는 JSON(Javascript Object Notion) 형태로 대체되어 데이터 통신을 한다.

     


    우선 콜백함수가 무조건 비동기로 처리된다고 막연하게 이해한 내 개념을 고치는 기회였다.

    또, 예전에 유튜브 강의(엘리님)로 무작정 공부했던 비동기(콜백지옥)를 다시 보니 지금은 확실히 와닿았다.

    특히, Promise(객체)나 Async/Await이 비동기 콜백을 개선하기 위해 순차적으로 등장했다는 배경도 확실히 알아간다.

     

    다음 포스팅에서는, Promise 객체와 Async/Await에 대해 정리하도록 하겠다!!

     

    [출처]

    - 코딩하는 락스타 님의 블로그 : corock.tistory.com/456

    - 태기 님의 블로그 : ljtaek2.tistory.com/142

    - PoiemaWeb: poiemaweb.com/js-async

    - 캡틴판교 : joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/

    - Youtube 드림코딩 엘리(11. 비동기 처리 및 콜백함수) : www.youtube.com/watch?v=s1vpVCrT8f4

     

    [비동기 포스팅 시리즈]

    - #2. Promise : abangpa1ace.tistory.com/76

     

    [Javascript] 비동기 - #2. Promise

    나도 그렇고, fetch()-then 이나 async/await 을 사용하면 최초에 Promise 라는 결과값이 반환되는 것을 볼 수 있었을 것이다. 또한, 자세히 보면 이 Promise는 객체(Object) 형태를 하고 있으며, 아래와 같이

    abangpa1ace.tistory.com

    - #3. Async/Await : abangpa1ace.tistory.com/83

     

    [Javascript] 비동기 - #3. Async & Await

    프로젝트를 마무리하고, 이제서야 비동기 3탄이자 꽃인 async/await 에 대해 포스팅을 한다. async/await 은 JS의 비동기 처리를 개선하기 위해(콜백지옥), Promise 처럼 ES6 이후 문법에서 등장했으며 가장

    abangpa1ace.tistory.com

     

    반응형
Designed by Tistory.