[JavaScript] TWIL : 비동기와 Promise (20/11.09~11.10)

정빈·2020년 11월 17일
0

본격적으로, Server 파트를 시작하게 되었다.
Server 파트의 첫 시작인, Asynchronous & Promise 스프린트를 진행했다.

동기? 비동기?

기본적으로 Javascript의 runtime(구동 환경)은 Single Thread라고 한다. 그게 뭔가 하면, 동시에 하나 이상의 일을 못한다는 말이다. (서버로부터 데이터를 받아오는데 10초가 걸린다면, 10초동안 아무것도 못한다는 것..)
그래서! 이를 해결하기 위해 비동기적 이벤트 실행을 지원한다.
그렇다면, '비동기'라는 무슨 뜻일까?

sync(동기 : blocking), async(비동기 : non-blocking)

동기적 처리는 순차적으로 일을 진행하는 process를 뜻한다. task1이 완료되고 나서야 tast2가 진행되고, task2가 완료되고 나서 task3가 진행된다. 여러 개의 task가 동시에 진행될 수 없고, 하나의 task를 진행하는 중에는 다른 모든 task가 막혀있는 방식이다.

반면에 비동기적 처리는, 시간이 걸려도 'ok! 다 되면 다시 불러(callback)!'하고 다음으로 넘어간다. 다른 동작을 하다가, 처리가 다 되었다하면 그 때 해당 동작을 마무리한다. 그림에서처럼 시간이 매우 단축된다. 묶여서 기다릴 필요가 없다.

실제로 웹페이지를 실행시키는데 페이지 로드가 한 땀 한 땀, 차근 차근 구성된다고 생각해보자. 당장 컴퓨터를 부수고 싶을지도 모른다. 비동기는 사용자들의 정신건강을 지켜주는 꼭 필요한 개념이다.

비동기로 실행되는 함수들의 반환 '순서'를 '제어'하는 방법

그런데, 이렇게 비동기적으로 실행되는 코드의 동작들의 '순서'를 '제어'하고 싶다면 어떻게 해야할까?

1. callback

제일 기본적으로 callback 방식이 떠오를 수 있다.
비동기 함수끼리 묶어서 순서를 정해준 뒤, 하나가 끝나면 뒤의 callback이 실행되게끔 엮어주면 될 것이다.
예제로 보면 이해가 쉽다.

let printString = (str, callback) => {
    setTimeout(() => {
        console.log(str)
        callback()
    }, 1000)
};

let printAll = () => {
    printString("I", () => {
        printString("Love", () => {
            printString("U!", () => {})
        })
    })
};

printAll(); // --> console에 1초 후  I, 2초 후 LOVE, 3초 후 U! 가 찍힌다.

하지만 코드의 구조를 보자. 저런 callback구조가 10개, 100개, 200개가 된다면..?
엄청난 callback Hell!을 마주하게 될 수 있다. (google images로 'callback hell'을 검색해보자. 처참한 형상을 띄고있는 코드들을 예제로 구경할 수 있다.)
callback 100개를 용써서 엮어놨는데, 59번째 callback을 수정하고 싶다면? 해야한다면? 눈알 빠진다. 그 날은 시력이 -1.0 되는 날.. ^^ 유지보수가 너무너무 힘들다.

2. Promise - then, catch

시력저하를 겪는 개발자들을 위해, Promise라는 개념이 도입되었다.
Promise는 비동기 실행이 끝나면, 성공(resolve)/실패(reject)에 따라 입력받은 함수를 실행해줄게!라고 약속해주는 '약속의 객체'이다.
성공한 경우 .then으로 다음 함수를 실행시키고, 실패한 경우 .catch로 실패시의 핸들링 함수를 실행시킬 수 있다.
위의 예제를 promise 객체를 이용해 리팩토링 해보자.

let printString = (str) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(str);
            resolve();
        }, 1000)
    })
};

let printAll = () => {
    printString("I")
    .then(() => {
        printString("Love")
    })
    .then(() => {
        printString("U!")
    })
    .catch(() => {  // 실패시 핸들링 하지 않을거면 생략 가능.
      // Error!
    })
}

printAll(); // --> console에 1초 후  I, 2초 후 LOVE, 3초 후 U! 가 찍힌다.

.catch는 항상 마지막에 엮어쓴다. 어느 단계든 실패 처리가 발생하면 catch로 귀결된다.
그리고, 앞서 실행된 함수의 return 값을 그대로 받아서 다음 함수를 실행시키고 싶다면, then안에 작성하는 함수에 인자를 넣게되면 앞 함수의 return 값이 인자로 들어온다.
chaining이 아름답게 가능하다.

3. async & await

async / await 는 promise보다 더 쉽게 쓸 수 있도록 나온 가장 최신! 개념이다.
async 키워드가 붙은 함수는, 함수 선언 내에서 await 키워드를 이용해 비동기 함수들을 인간으로 하여끔 동기적으로 보이게 순서를 제어해주는 chaining이 가능하다.
위의 예제를 리팩토링한 예제로 살펴보자.

let printString = (str) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(str);
            resolve();
        }, 1000)
    })
}

async function printAll() {
  let one = await printString("I");
  // console.log(one)
    
  let two = await printString("Love");
  // console.log(two)
  
  let three = await printString("U!");
  // console.log(three) 
}

printAll(); // --> console에 1초 후  I, 2초 후 LOVE, 3초 후 U! 가 찍힌다.
            // 주석처리 해놓은 console.log를 주석 해제하고 실행시켜보자. 재밌는 현상이 일어난다.

즉, async / await는 promise객체를 그대로 받아서, chaining을 할 때 비동기 함수들을 동기적으로(순서대로) 보이게 하는 키워드이다. 순서의 제어가 훨씬 직관적이고 쉽게 가능하다.


앞서 언급했듯, 웹 개발에서 비동기 실행은 필수불가결한 개념이다. 잘 익혀두자!
profile
Back-end. You'll Never Walk Alone.

0개의 댓글