끄적끄적

[JAVASCRIPT] async/await & promise & generator 에 대한 정리 본문

Front-end/Javascript

[JAVASCRIPT] async/await & promise & generator 에 대한 정리

mashko 2021. 5. 4. 00:11
반응형

자바스크립트의 비동기 프로그래밍에 대해서 정리해봅시다.
async/await & promise & generator 각각은 문법과 특성이 있습니다.
javascript는 Event Driven으로 실행되는 구조를 가지고 있습니다.
어떠한 이벤트를 감지 했을 때 실행되며 Event Driven에 의한 특징으로 콜백지옥이 생기게 되죠.

이러한 콜백지옥을 해결하기 위해 생긴것이 비동기 프로그래밍입니다. 하나하나 정리해보죠.
먼저 콜백지옥이 어떤 것이냐..?

var num = 1
setTimeout(() => {
  console.log(`${++num}`)
  setTimeout(() => {
    console.log(`${++num}`)
    setTimeout(() => {
      console.log(`${++num}`)
      ...
    }, 1000)
  }, 1000)
}, 1000)

.... 감이 오시나요?
위와 같은 패턴이 지속된다면 무한 콜백으로 이어지는데.. 이 부분을 조금 더 가독성이 좋고, 조금 더 편하게 쓸 필요가 있죠.
그래서 해소하기 위해 대안으로 나온것이 async/await & promise & generator와 같은 비동기 프로그래밍 기법입니다.

Promise
먼저 Promise 패턴입니다. new Promise를 통해 생성하고 resolve와 reject를 통해 콜백의 성공/실패 케이스를 작성합니다.

const myFirstPromise = new Promise((resolve, reject) => { 
    const condition = true;   
    if(condition) {
         setTimeout(function(){
             resolve("Promise is resolved!"); // fulfilled
        }, 300);
    } else {    
        reject('Promise is rejected!');  
    }
});

myFirstPromise
.then((successMsg) => {
    console.log(successMsg);
})
.catch((errorMsg) => { 
    console.log(errorMsg);
});

// 또는

const promiseAll = [myFirstPromise, myFirstPromise, myFirstPromise];
Promise.all(promiseAll)
    .then((success) => {
        console.log(success[0], success[1], success[2]);
    });

위와같이 Promise 패턴을 이용해서 콜백 지옥을 해소 할 수 있습니다.
하지만 Promise 패턴도 단점이 있습니다. 아래의 예제를 보죠

test.then(myFirstPromise).then(myFirstPromise).then(myFirstPromise).then(myFirstPromise)....

처음보다는 괜찮은데 그래도 좀 더.. 깔끔하게 하고 싶죠..

Async/Await
Await는 기본적으로 Promises의 구문 바탕입니다. 비동기 코드를 async/await을 통해 가독성을 높여주며 그로인해 이해하기 더 쉽습니다.

const myFirstPromise = new Promise((resolve, reject) => { 
    const condition = true;   
    if(condition) {
         setTimeout(function(){
             resolve("Promise is resolved!"); // fulfilled
        }, 300);
    } else {    
        reject('Promise is rejected!');  
    }
});
const myTwoPromise = new Promise((resolve, reject) => { 
    const condition = true;   
    if(condition) {
         setTimeout(function(){
             resolve("Promise is resolved!!!"); // fulfilled
        }, 300);
    } else {    
        reject('Promise is rejected!');  
    }
});
async function example() {
    try {
        let message = await myFirstPromise;
        console.log(message); // Promise is resolved!
        message = await myTwoPromise;
        console.log(message); // Promise is resolved!!!
    } catch(error) {
        console.log(error.message);
    }
}
example();

위와 같이 async/awati을 통해 조금 더 가독성 좋고 계획적으로 프로그래밍을 할 수 있습니다. Promise와 async/await을 별개로 놓고 쓰지 않아도 되며, 조금 더 유용하게 쓸 수 있습니다.

generator
제너레이터는 반복 가능한 이터레이터(value 프로퍼티와 done 프로퍼티를 가진 객체)를 값으로 반환하고, 작업의 일시 정지와 재시작이 가능함과 동시에 자신의 상태를 관리하며, 비동기 특성을 동기적 코드방식으로 관리 해줍니다.(async/await의 동작과 비슷함)
generator는 함수에 *를 통해 선언하고 yield를 통해 정의합니다. async/await은 함수 앞에 async를 통해 선언하고 await을 정의합니다.

  • iterable
    Symbol.iterator 심볼을 속성으로 가지고 있고, 이터레이터 객체를 반환하는 객체입니다.
  • iterator
    next() 메서드를 구현하고 있고, done과 value 속성을 가진 객체를 반환하는 객체이고,
    Symbol.iterator 메소드를 호출하여 이터레이터 객체를 얻은 후, 순차적으로 next() 메소드를 호출하면서 하나씩 순회하며, done 값이 true 이면 순회를 멈춥니다.
const iterable = {
  [Symbol.iterator]() {  
    let step = 0;
    return {
      next() {
        step++;
        if (step === 1) {
          return { value: 'hi', done: false};
        } else if (step === 2) {
          return { value: 'iterable', done: false};
        } else if (step === 3) {
          return { value: 'bye.', done: false};
        }
        return { value: '', done: true };
      }
    }
  },
}
for (const val of iterable) {
  console.log(val);
}
// hi
// iterable
// bye

이터러블 객체를 구현한 코드를 제너레이터 함수에 비교해서 만들어보죠

function *iterable() {
  yield 'hi';
  yield 'iterable';
  yield 'bye';
}
for (const val of iterable()) {
  console.log(val);
}
// hi
// iterable
// bye

위와 같이 결국 동작 방식은 같습니다. 제너레이터는 이터러블(iterable)이면서 동시에 이터레이터(iterator)인 객체입니다.
그럼 제너레이터를 응용해보겠습니다.

const myFirstPromise = new Promise((resolve, reject) => { 
    const condition = true;   
    if(condition) {
         setTimeout(function(){
             resolve("Promise is resolved!"); // fulfilled
        }, 300);
    } else {    
        reject('Promise is rejected!');  
    }
});
const myTwoPromise = new Promise((resolve, reject) => { 
    const condition = true;   
    if(condition) {
         setTimeout(function(){
             resolve("Promise is resolved!!!"); // fulfilled
        }, 300);
    } else {    
        reject('Promise is rejected!');  
    }
});
function* example() {
    yield myFirstPromise;
    yield myTwoPromise;
}

const request = example();

request.next().value.then((message) => {
    console.log(message); // Promise is resolved!
})
.catch((error) => {
    console.log(error.message);
});

request.next().value.then((message) => {
    console.log(message); // Promise is resolved!!!
})
.catch((error) => {
    console.log(error.message);
});

위와 같이 Promise와 Generator를 통해 비동기 프로그래밍에 응용 할 수 있습니다.
한가지만을 사용하는게 아닌 async/await & promise & generator를 통해 적절한 상황에 따라 사용한다면 조금 더 멋진 결과를 낼 수 있습니다.

추가 참고 자료

반응형
Comments