JavaScript

비동기 작업의 동기적 표현 - Promise, Generator, Async/await

차돌박이츄베릅 2023. 5. 28. 10:50

동기(synchronous)

  • 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식을 말해요!
  • CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드구요.
  • 계산이 복잡해서 CPU가 계산하는 데에 오래 걸리는 코드 역시도 동기적 코드에요

 

비동기(a + synchronous ⇒ async)

  • 실행 중인 코드의 `완료 여부와 무관하게` 즉시 다음 코드로 넘어가는 방식
  • setTimeout, addEventListner 등
  • 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드

 


1. Promise

비동기 처리에 대해, 처리가 끝나면 알려달라는 ‘약속’

  • new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행돼요.
  • 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않아요. => 즉, resolve가 실행되어야 다음으로 넘어감
  • 따라서, 비동기작업이 완료될 때 비로소 resolve, reject 호출해요.

 

[1_promise 사용예제]

/*
Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 '약속'
new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행됨

resolve : 성공. 예)naver에서 날씨 정보를 잘 얻어왔을 경우
reject : 실패. 예)naver서버가 다운되어 응답을 줄 수 없을 때

처리 결과에 따라서 오류사항들을 tracking할 수 있음
promise.then : 다음
promise.catch : 오류
*/

// resolve, reject가 인자로 들어갈 수 있음
new Promise(function (resolve) {
    setTimeout(function () {
        var name = '에스프레소';
        console.log(name);
        resolve(name); // resolve가 실행되어야 다음으로 넘어가기 때문에 resolve()해주면 됨. 여기서도 괄호에 인자 넘겨줄 수 있음

        // resolve()가 있다는 것은 then()으로 받을 수 있다는 뜻
        // then()안에도 마찬가지로 콜백함수가 들어감
    }, 500);
})
    .then(function (prevName) {
        // prevName은 resolve()에서 받아온 인자
        //
        // 이 안에서도 새롭게 promise를 만들어요!
        // 계속 chaning을 걸기 위해
        // return을 해서 새롭게 이 function에 대한 return문을 만들어줌
        // (ㄴ근데 return없어도 즉시 실행되는데 왜 넣어주는거지??)

        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ', 아메리카노';
                console.log(name);
                resolve(name);
            }, 500);
        });
    })
    .then(function (prevName) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ', 카페모카';
                console.log(name);
                resolve(name);
            }, 500);
        });
    })
    .then(function (prevName) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ', 카페라떼';
                console.log(name);
                resolve(name);
            }, 500);
        });
    });

 

[2_promise 리팩토링 중간]

반복 부분을 함수화하기

// refactoring : 비효율적인 코드를 효율적인 코드로 변경하는 작업
// re : 다시
// factoring : 짓는다. 구조화

// 반복되는 로직들을 함수화시키면 어떨까?
// 커피 이름을 반복해서 출력하는거니까 커피를 추가하는 addCoffee함수를 만듦
var addCoffee = function (name) {
    // 요점⭐반복되는 로직에서 계속 바뀌는 부분은 한 군데 밖에 없음. 아메리카노, 카페라떼 ... 바뀌는 부분이 변수니까 이 변수부분을 우리가 커피이름을 받겠다.
    // 요점⭐name을 받아서 필요한 로직을 return하게 하면 됨
    //
    // 이 함수를 호출할 때 변수로 계속 넣을만한거는 아메리카노, 카페라떼 ...
    // 이 이름들을 변수로 넣을 수 있도록 이름 name을 넣어줌
    // 이 이름을 기반으로 내부에 로직을 생성하게끔 함
    // return 반복할 로직 =>하면 반복할 수 있게 뱉어냄

    return function (prevName) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ', ' + name; // 아메리카노, 카페라떼 대신에 우리가 받아온 name으로 갈아끼우기
                console.log(name);
                resolve(name);
            }, 500);
        });
    };
};

// addCoffee('에스프레소')

 

[3_promise 리팩토링 최종]

// var addCoffee = function (name) {
var addCoffee = (name) => {
    return function (prevName) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                var newName = prevName ? `${prevName}, ${name}` : name;
                console.log(newName);
                resolve(newName);
            }, 500);
        });
    };
};

// 처음 시작하려면 두번째return문의 new Promise~ 부터 시작해야되니까
// 괄호를 두번 닫아줘야 두번째return 까지 실행되는겨
// addCoffee('에스프레소')();
addCoffee('에스프레소')().then(addCoffee('아메리카노')).then(addCoffee('카페모카')).then(addCoffee('카페라뗴'));

 

 

 

 


2. Generator

  • *가 붙은 함수가 제너레이터 함수입니다. 
    제너레이터 함수는 실행하면, Iterator 객체가 반환(next()를 가지고 있음)돼요. 
  • iterator 은 객체는 next 메서드로 순환 할 수 있는 객체구요. 
    next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield에서 stop 이후 다시 next 메서드를 호출하면 멈췄던 부분 -> 그 다음의 yield까지 실행 후 stop 
  • 즉, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 -> 아래 순차적으로 진행돼요
/*
제너레이터 문법은 반복할 수 있는 Iterator객체를 생성한다.

Iterator객체의 next()메서드 : 이걸 이용해서 자기자신을 계속해서 순환할 수 있음

yield 양보하다, 미루다.

비동기 작업이 완료되는 시점마다 next메서드를 호출해서 위에서 아래로 순차적으로 시행할 수 있음.

비동기적인 요소를 왜 동기적으로 바꾸려고 하는가?
순서를 보장할 수 없으니까
순서 보장에 필요한 어떤 로직에서
순서를 보장 받기 위해서 그렇게 한다.
*/

// (1) 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
// 500초 뒤에 next 날려서 실행 + 이름 넘겨주는 용도인가?
var addCoffee = function (prevName, name) {
    setTimeout(function () {
        coffeeMaker.next(prevName ? prevName + ', ' + name : name); // next의 인자 값은 yield에 전달됨
    }, 500);
};

// (2) 제너레이터 함수 선언 !!!
// yield 키워드로 순서 제어
//
// *가 붙은 함수가 제너레이터 함수입니다.
// 이 함수를 실행하면 -> Iterator 객체가 반환됩니다.
// coffeeGenerator는 제너레이터 함수인데 이걸 괄호닫아서 실행하면
// var coffeeMaker = coffeeGenerator()에서 coffeeMaker는 Iterator객체를 갖고 있는다는 것
var coffeeGenerator = function* () {
    var espresso = yield addCoffee('', '에스프레소'); // yield 키워드를 만나면 멈춤 ! yield뒤 문구가 끝날 때 까지 기다린 다음에 다시 next메서드로 호출하면 다시 멈췄던 부분까지 감. addCoffee안에 next가 있어서 stop을 걸어주는 셈
    console.log(espresso);
    var americano = yield addCoffee(espresso, '아메리카노');
    console.log(americano);
    var mocha = yield addCoffee(americano, '카페모카');
    console.log(mocha);
    var latte = yield addCoffee(mocha, '카페라떼');
    console.log(latte);
};
var coffeeMaker = coffeeGenerator(); // Iterator객체
coffeeMaker.next(); // Iterator는 next메서드로 계속해서 순환할 수 있는 객체
// next()로 한번 실행을 시켜주기만 하면 내부로 들어가는거 Line31 -> 17
// next 수행 <->  yield 기다림

 

 

 


3. Promise + Async/await

비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 됨.

Promise ~ then과 동일한 효과를 얻을 수 있음

/*
이전에 썼던 Promise 방법 : resolve(성공) / then(그러면~~)
이번에 쓸 async(비동기) 방법 : await(기다리다)
*/

// coffeeMaker 함수에서 호출할 함수, 'addCoffee'를 선언
// 이 함수는 Promise를 반환함
var addCoffee = function (name) {
    // 목적: 500초 기다림 + 커피이름 퉤
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(name);
        }, 500);
    });
};

// ⭐
// async라는 키워드를 붙여주게되면
// {}중괄호가 시작되는 스코프 안에
// await란 키워드를 만난 뒤의 그 메서드_addCoffee('에스프레소')는
// 그 메서드가 끝날 때까지 무조건 기다리게 되어 있습니다.
// 근데 그 메서드는 항상 Promise를 반환해야만 함
var coffeeMaker = async function () {
    // var coffeeMaker = async () => {
    // 여기의 async는 아래 코드를 동기적으로 순차실행하기 위함
    var coffeeList = '';

    // 목적: 커피이름 받은걸로 합쳐합쳐~
    // 여기의 async는 Promise로 받아오는데 걸리는 시간까지 기다리기 위함
    var _addCoffee = async function (name) {
        coffeeList += (coffeeList ? ', ' : '') + (await addCoffee(name));
    };

    // promise를 반환하는 함수인 경우, await를 만나면 무조건 끝날 때 까지 기다린다.
    // 1. _addCoffee('에스프레소') 이 로직이 실행되는데 100초가 걸렸다면
    await _addCoffee('에스프레소');

    // 2. 아래의 console.log(coffeeList)는 100초 뒤에 실행되게 됨.
    console.log(coffeeList);
    await _addCoffee('아메리카노');
    console.log(coffeeList);
    await _addCoffee('카페모카');
    console.log(coffeeList);
    await _addCoffee('카페라떼');
    console.log(coffeeList);
};
coffeeMaker();

 

'JavaScript' 카테고리의 다른 글

클로저  (0) 2023.05.28
Class  (0) 2023.05.28
콜백 함수  (0) 2023.05.28
this 바인딩 - call, apply, bind  (0) 2023.05.25
실행컨텍스트(스코프, 변수, 객체, 호이스팅)  (2) 2023.05.25