ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JavaScript Visualized: Generators and Iterators
    et cetera/TIL 2021. 11. 27. 18:37
    반응형

    Generators and Iterators

    일반 함수는 호출하면 바로 완료까지 실행되는 'run-to-completion' 모델.

    제너레이터 함수(function*)는 함수 실행 중간중간에 yield 키워드로 pause가 가능.

    제너레이터 함수는 iterator인 제너레이터 객체를 반환. (사실 iterator이면서 iterable이다.)

    iterator이므로 next 메서드를 가진다.

     

    next 메서드를 호출하여 값을 직접 출력해보면 value와 done 프로퍼티를 가진 객체(iterator result)가 반환된다.

    value는 yield(혹은 return) 해주는 값들과 동일하고, done은 yield가 아닌 return일 때만 true를 가지는 boolean 값.

    yield 키워드가 나오면 제너레이터를 순회하는 것을 멈추기 때문에 함수가 중지된 것처럼 보이는 것.

     

    제너레이터 함수를 호출하여 변수에 할당한 제너레이터 객체는, 한 번밖에 순회를 하지 못한다.

    return 이후에는 next 메서드를 호출해도 { value: undefined, done: true } 만 계속 반환한다.

    새로 순회를 하고싶다면 제너레이터 객체를 새로 만들어줄 것.

     

    제너레이터 객체는 iterator이므로 당연히 spread operator와 for ... of 문의 사용이 가능.

    function* getEmojis() {
        yield '🎄';
        yield '🎁';
        yield '😀';
    }
    
    const genObj = getEmojis()
    [...genObj] // ['🎄', '🎁', '😀']

    iterable한 객체는 [Symbol.iterator] 메서드를 소유해야하고

    [Symbol.iterator] 메서드는 iterator를 return해야 한다.

    iterator는 { value: '...', done: true/false }를 return하는 next 메서드를 가진다.

     

    [Symbol.iterator]를 구현하기 귀찮다면, 그냥 제너레이터 함수로 만들어주면 된다.

    제너레이터 함수는 default로 iterator를 반환하니까.

    해당 객체 자체를 yield 해주면 된다.

    object[Symbol.iterator] = function* () {
    	yield this
    }

    추가로, yield* 키워드를 사용하면(뒤에 asterisk 붙음) 또 다른 iterable 객체의 값들을 개별적으로 yield 해줄 수 있다.

    const emojis = ['🎄', '🎁', '😀']
    
    function* getEmojis() {
    	yield '😥'
        yield* emojis
        yield '😍'
    }
    
    const result = getEmojis()
    [...result] // ['😥', '🎄', '🎁', '😀', '😍']

     

    매우 큰 데이터를 순회하면서 분석하는 경우에 제너레이터를 사용하면 효율적으로 순회할 수 있다.

    큰 데이터를 여러 개의 작은 데이터 조각으로 쪼개서, 일부를 요청하고, 분석한다.

    그리고 next를 호출하면 다음 데이터 조각을 요청한다.

    const bookClubs = [
    	{
    		name: "The Cool Club",
    		clubMembers: [
    			{
    				name: "John Doe",
    				books: [
    					{ id: "hs891", title: "A Perfect Book" },
    					{ id: "ey812", title: "A Good Book" },
    				]
    			}
    		]
    	},
    	{
    		name: "The Better Club",
    		clubMembers: [
    			{
    				name: "Jane Doe",
    				books: [
    					{ id: "u8291", title: "A Great Book" },
    					{ id: "p9201", title: "A Nice Book" },
    				]
    			}
    		]
    	}
    ]
    function* iterateBooks(books) {
    	for (let i = 0; i < books.length; i++) {
    		yield books[i]
    	}
    }
    
    function* iterateMembers(members) {
    	for (let i = 0; i < members.length; i++) {
    		const clubMember = members[i]
    		yield* iterateBooks(clubMember.books)
    	}
    }
    
    function* iterateBooksClubs(bookClubs) {
    	for (let i = 0; i < clubs.length; i++) {
    		const bookClub = booksClubs[i]
    		yield* iterateMembers(bookClub.clubMembers)
    	}
    }
    
    function findBook(id) {
    	const genObj = iterateBookClubs(bookClubs)
    	let result = genObj.next()
    	
    	while (!result.done) {
    		if (result.value.id === id) {
    			return result.value
    		} else {
    			result = genObj.next()
    		}
    	}
    }
     

    💡🎁 JavaScript Visualized: Generators and Iterators

    ES6 introduced something cool called generator functions 🎉 Whenever I ask people about generator func...

    dev.to

     

    영상 자료로는 우아한테크코스 크루인 '파노'의 테코톡도 추천.

    영상의 마지막 부분을 보면, Generator가 어떻게 일시중지했다가 중단 시점부터 다시 실행할 수 있는지가 나온다.

    next 메서드를 호출할 때마다 매번 새로운 실행 컨텍스트를 생성하는 것이 아니라,

    실행 컨텍스트를 하나 만들어 두고, next 메서드가 호출될 때마다 그걸 계속 재활용 하는 방식이기 때문이라고 함.

    (동일한 실행 컨텍스트를 계속 call stack에 push 했다가, 제거했다가, ... 를 반복하기 때문)

    반응형

    댓글

Designed by Tistory.