티스토리 뷰

이 내용은 아래 링크에서 동영상 강의도 같이 보실 수 있습니다.

[ 동영상 강의 보기 ]

Generator

generator는 임의로 실행을 정지했다 다시 실행할 수 있는 함수입니다.

함수 실행을 정지/재개 하면서 함수 내부로부터 값을 가져오거나 집어넣을수 있어 다양한 목적으로 사용할 수 있습니다.

generator를 정의하는 방법은 아래와 같습니다.

function* numbers() {
    console.log('시작');
    yield 1;
    yield 2;
    yield 3;
}

function* 키워드로 선언하고 일반 함수와는 전혀 다르게 동작합니다.

console.log(numbers());

함수를 실행하고 결과를 출력해보면 내부 코드가 전혀 실행되지 않고 Generator 객체가 리턴됩니다.

내부 코드를 실행하려면 리턴된 Generator객체의 next() 메소드를 호출해야 합니다.

next 메소드는 다음 yield나 return문을 만나거나 블록이 닫힐때까지 실행합니다.

let n = numbers();
console.log(n.next());
// "시작"
// {value: 1, done: false}
console.log(n.next());
// {value: 2, done: false}
console.log(n.next());
// {value: 3, done: false}
console.log(n.next());
// {value: undefined, done: true}

처음 next()를 호출하면 numbers함수에서 "시작"을 출력 후 next() 메소드가 리턴한 {value: 1, done: false} 가 출력됩니다.

next()메소드가 리턴하는 json객체의 value는 yield가 리턴한 값을 의미하고 done은 코드가 끝까지 실행됐는지 여부를 나타냅니다.

next()를 호출할때마다 같은 방법으로 json객체를 리턴하다 더이상 실행할 코드가 없을 경우 {value: undefined, done: true}를 리턴합니다.

만약 블록이 끝나기 전 return문을 만났다면 {value: [return값], done: true}를 리턴합니다.

다른 예제를 하나 더 보겠습니다.

function* numbers(x) {
    x = yield x;   // 1번
    x = yield x;   // 2번
    yield x;       // 3번
}

let n = numbers(1);

console.log(n.next());
// {value: 1, done: false}
console.log(n.next(2));
// {value: 2, done: false}
console.log(n.next(3));
// {value: 3, done: false}
console.log(n.next());
// {value: undefined, done: true}

이 코드는 아래와 같이 동작합니다.

let n = numbers(1);

Generator객체를 만들고 x변수의 값을 1로 세팅합니다.

console.log(n.next());

next메소드가 호출되어 다음 yield문(1번)을 만날때까지 실행하고 현재 x변수값이 1이고 아직 코드가 끝나지 않았으니 {value: 1, done: false}를 리턴합니다.

console.log(n.next(2));

next메소드는 파라메터로 받은 값을 가장 마지막에 만난 yield문에 대입합니다. 그러므로 1번라인에 있는 yield x는 파라메터로 넘겨받은 2로 변경되어 실제 동작은 x = 2로 되고 다시 다음 yield문까지 실행하여 x변수값인 2와 코드종료 플래그를 담아 {value: 2, done: false}를 

console.log(n.next(3));
console.log(n.next());

같은 방식로 동작하여 yield문에 3을 대입해서 {value: 3, done: false}를 리턴하고 그 다음 next는 더이상 실행할 코드가 없으니 {value: undefined, done: true}를 리턴합니다.

Generator를 어디다 사용해야 할까?

generator는 객체를 iterable하게 만들어 for문에 사용할 수 있도록 만들수도 있고, 복잡한 loop문을 리팩토링하는데 사용할수도 있습니다.

다양한 사용방법이 있긴 하지만 여기서는 앞서 설명드린 비동기처리를 동기코드처럼 개발하는 방법에 대해 알아보겠습니다.

- 구현방법

비동기처리를 동기코드처럼 개발하는 구현방법은 간단합니다.
Generator가 정지했다 다시 실행할 수 있는 함수이니 비동기처리가 필요한 시점에 정지시키고 비동기처리가 모두 끝난 뒤 다시 실행하면 됩니다.

- 예제 소스

$.ajax({
    url: '/api/current-user',
    success: function(user) {
        const userId = user.id;

        // 넘겨받은 user_id를 기반으로 친구 목록 가져옴
        $.ajax({
            url: '/api/friend-list',
            data: {
                user_id: userId
            },
            success: function(friends) {
                let i;

                for (i = 0; i < friends.length; i ++) {
                    const friendId = friends[i].id;

                    // 넘겨받은 user_id를 기반으로 최근 구매항목 가져옴
                    $.ajax({
                        url: '/api/latest-purchase-item',
                        data: {
                            user_id: friendId
                        },
                        success: function(item) {
                            console.log(item.name);
                        }
                    })
                }
            }
        })
    }
});

Promise편에서 보여드렸던 Promise를 적용하지 않은 예제소스입니다.

axios.get('/api/current-user').then(result => {
    return axios.get('/api/friend-list', {user_id: result.data.id});
}).then(result => {
    let i;
    const friends = result.data;
    let queries = [];
 
    for (i = 0; i < friends.length; i ++) {
        queries.push(axios.get('/api/latest-purchase-item', {user_id: friends[i].id}));
    }
    return Promise.all(queries);
}).then(results => {
    console.log(results);
});

이 코드역시 Promise편에서 보여드렸던 Promise를 사용해서 좀 더 보기좋게 만든 코드입니다.

여기서 비동기작업이 시작되는 부분인 Promise객체를 생성하는 시점에 generator함수를 정지시키고 실행 결과를 가져오는 부분인 then에서 다시 함수를 시작시키면 generator함수 내부의 코드는 모두 동기코드처럼 만들 수 있을것입니다.

function* getItemsGen() {
    const user = yield axios.get('/api/current-user');
    const friends = yield axios.get('/api/friend-list', {
        user_id: user.id
    });
    let queries = [];

    for(let i = 0; i < friends.length; i ++) {
        queries.push(axios.get('/api/last-purchase-item', {
            user_id: friends[i].id
        }));
    }

    const items = yield Promise.all(queries);
    console.log(items);
}

이렇게 Promise객체를 생성하는 코드 앞에 yield만 붙여주면 매번 next가 호출될때마다 Promise객체를 리턴하고 함수를 정지시킵니다.

const getItems = getItemsGen();

new Promise((resolve, reject) => {
    next();

    function next(v) {
        const ret = getItems.next(v); // 1번

        if (ret.done === true) { // 2번
            resolve();
        } else { // 3번
            ret.value.then(next);         
        }
    }
});

generator함수를 실행하는 코드입니다.

next함수는 생성한 generator객체가 모두 실행될때까지 next를 호출해주는 역할을 합니다.

1번 코드에서 next메소드를 호출한 뒤 결과값을 ret이란 변수에 저장합니다.

그리고 만약 generator내부 코드가 모두 실행됐다면 resolve함수를 호출해 Promise 실행을 종료시키고, 코드가 남아있다면 promise객체의 then에 next함수를 연결해 동일한 동작을 반복해줍니다.

- co

이런 generator함수 실행을 도와주는 라이브러리입니다. [ github 페이지 링크 ]

co를 사용하면 generator실행하는 코드를 별도로 만들 필요 없이 아래와같이 실행해주시면 됩니다.

co(function* () {
    const user = yield axios.get('/api/current-user');
    const friends = yield axios.get('/api/friend-list', {
        user_id: user.id
    });
    let queries = [];

    for(let i = 0; i < friends.length; i ++) {
        queries.push(axios.get('/api/latest-purchase-item', {
            user_id: friends[i].id
        }));
    }

    const items = yield Promise.all(queries);
    console.log(items);
});


댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함