[해당 이미지는 다음 링크에서 사용했습니다 : http://welllin.net/javascript-es6-generator/]
#제네레이터 함수 #제네레이터 함수는 무엇인가 #yield란 #제네레이터함수 작동 #yield 표현식 #next()호출
이 블로그는 제네레이터 함수 공부하며 참고한 블로그 내용을 재 가공해 작성 한 글입니다.
중복되는 용어와 해당 부분에 대해 링크를 정리했습니다.궁금하신 분은 주소를 클릭하면 원문을 읽을 수 있습니다.
지난 글 이론편에서 “어디에” 제네레이터 함수를 써야하는가?를 다뤘습니다.
오늘 우리는 제네레이터 함수가 무엇이고 어떻게 동작하는지에 대해 하나씩 살펴볼 것 입니다.
블로그를 이론적인 부분과 코드편으로 나눠 작성됩니다. 현재 이론편을 다루고 있습니다.
(각각의 모든 글 하단에 참고&인용한 블로그 링크를 추가했습니다.)
[1]. “왜” 제네레이터 함수를 써야 하는가?
[2]. “어디에” 제네레이터 함수를 써야하는가?
[3]. 제네레이터 함수의 "정의"와 "작동원리"는 무엇인가?
[이전 글에서]
1. 제네레이터 함수란 무엇인가?
2. 제네레이터는 어떻게 작동할까?
함수를 호출하는 대상을 caller 라고 하고, 호출된 대상을 callee 라고 합니다.
코드를 보면,
function * generate(){
let a = yield 20
}
const generator = generate();
generator.next(10);
console.log(a)
제네레이트 함수 generate는 "callee" 이며
반면, 이를 호출하는 generator "caller" 라고 불린다.
caller는 제네레이터 객체를 가지고 있는 대상을 가리킵니다.
generator(caller)는 next() 메서드를 호출함으로써 generate(callee)의 yield 이후를 제어 할 수 있습니다.
yield 표현식의 결괏값은 caller인 generator에게 전달됩니다.
[Generator 함수를 이해해보자 (이론편).B]에서 next메서드 파라메타 값이 있을 경우, 이 값이 yield 왼쪽에 있는 변수의 값으로 할당된다고 말했습니다.
즉. [returnValue] 는 next()의 파라메타 값 입니다.
next() 메서드를 호출하는 목적은 이터러블한 객체의 값(value)을 "하나씩" "출력하는" 것 입니다. 하지만 제네레이터의 사용성이 단지 객체안의 값을 출력하는 경우에만 한정 지울 수 없습니다.
블로그 첫번째 편-"왜 제네레이트 함수를 사용할까"에서 언급한 것 처럼 제네레이트 함수는 "비동기 프로그래밍을 "동기적 코드"로 관리하는 장점 때문에 사용해야 된다고 말했습니다.
그런 관점에서 next() 메서드를 호출하는 의미를 "무엇을 요청한다"라고 생각하면 어떨까 생각합니다.
대표적인 비동기 프로그래밍은 서버에서 데이터를 가져와 처리하는 작업입니다. 비동기 코드는 어떤 일을 일어나게 합니다.
비동기 프로그래밍 (데이터베이스에 있는 데이터를 요청 후 처리,서버에 리퀘스트를 보내 데이터를 요청작업)을 제네레이터로 해결 할 수 있다고 생각합니다.
콜백 없이 제너레이터는 어떻게 파일(또는 데이터베이스) 작업의 결과 또는 서버 작업의 결과를 받을 수 있을까요?
이미 next()메서드의 파라미터 값으로 yield 왼쪽 변수에[returnValue]를 전달한다고 설명했습니다.
해당 부분은 next()메서드를 활용하는 사례에 초점을 둡니다
제네레이터 함수 안에 서버와 데이터베이스에 접근하는 코드를 작성하고 next()메서드로 리퀘스트와 DB작업을 요청하는 과정에서
next()메서드 인자여부에 따라 구분해봅니다.
next()메서드로 서버 혹은 데이터 베이스의 값을 요청하면,{value:"hello",done"false|true"} 형태로 결괏값을 받습니다.
제네레이터 객체를 가지고 있는 caller가 주도권을 잡고 필요한 데이터를 요청하는 형태 입니다.
제너레이터의 .next() 구문을 호출하는 코드(caller)가 제너레이터(callee)에게 어떤 값을 전달 하는 형태입니다.
이 next()인자가 제너레이터(callee) 입장에서는 yield 구문의 리턴 값처럼 보인다는 것입니다. 이제 제네레이터 함수 안의 yield 구문은 return 구문과 완전히 다른 형태가 됩니다. yield 구문은 제너레이터가 동작을 재개할 때 값을 반환하는 형태가 됩니다.
next() 인자가 없는 경우와 있는 경우를 요약하면 다음의 내용과 같습니다.
# next() 인자가 없는 경우
: 서버의 데이터를 요청합니다!
: DB의 값을 가져와 주세요!
# next() 인자가 있는 경우
: 서버에서 어떤 값(인자에 넣은 값)을 요청합니다!
: DB에서 어떤 값(인자에 넣은 값)을 가져와주세요!
예를 들어, 다음의 코드가 있다고 가정합니다.
var results = yield getDataAndLatte(request.areaCode);
: yield와 함수를 같이 사용해 비동기 프로그래밍을 할 수 있습니다.
위 이미지는 당시 이렇게 작성되지 않았나 생각합니다.
function * generate(){
var results = yield getDataAndLatte(request.areaCode);
var new-results = yield getDataAndLatte(results)
}
const generator = generate();
generator.next();
generator.next({data:[...thousands of records...],coffee:[object TravelMug]})
function * generate(){
var results = yield getDataAndLatte(request.areaCode);
}
const generator = generate();
generator.next();
#첫번째 next()메서드
1. 제네레이터 함수 generate를 실행시킵니다.
함수 getDataAndLatte(request.areaCode)가 호출되고 결과로
{value : "get me the database records for area code 614. and a nonfat vanilla latte!", done:false}
를 출력 됩니다.
제네레이터 함수 generate는 잠시 종료가 되고, 임의의 시간이 지날 수 있습니다.
function * generate(){
var results = yield getDataAndLatte(request.areaCode);
var new-results = yield getDataAndLatte(results);
}
const generator = generate();
generator.next();
generator.next({data:[...thousands of records...],coffee:[object TravelMug]})
#두번째 next()메서드
2.두번째 next()메서드를 호출하면서 인자로 {data:[...thousands of records...],coffee:[object TravelMug]}을 전달합니다. 이것이 result의 값으로 설정됩니다.
generator.next({data:[...thousands of records...],coffee:[object TravelMug]})
//result = {data:[...thousands of records...],coffee:[object TravelMug]}
동시에 result을 함수 getDataAndLatte의 인자로 요청합니다.
yield getDataAndLatte(results);
다음과 같이 결과를 반환합니다.
{value:"now update recodrd 7173 to ready status!",done: false}
yield와 next()의 관계를 어떻게 설명할까 생각하다가, 신호 신호등과 탁구를 이미지로 설명하려고 합니다.
"빨간불이 들어옵니다.
'잠시 멈추세요!'
신호등에 빨간불이 들어오면 걸어가던 길을 멈추게 됩니다. 동일하게 제네레이터 함수는 yield를 만나면 잠시 실행을 멈춥니다.
#파란불이 들어옵니다
'이제 건너가도 됩니다!'
그러다 next()를 만나면 제네레이터 함수는 중단된 지점에서 다시 함수 실행을 재개합니다. 잠시 멈췄던 발걸음을 다시 이어 갑니다.
톡~탁~톡~탁~
테이블을 사이로 두 사람이 공을 주고 받습니다.
생각해보세요, 제네레이터 함수에서는 값(value)을 가지고 yield가 값을 전달하면 받고 next()메서드가 보내주면 제네레이터 함수가 받습니다. 마치 탁구처럼 값이 오고 갑니다.
yield 표현식 값은 next()을 호출 해야만 받을 수 있고 next(인자)처럼 next의 파라메타 값으로 값을 보냅니다.
다시 말해, yield 와 next()가 서로 유기적으로 움직이고 있습니다.
그럼 우리가 생각해볼 이슈가 있습니다.
그 이유에 대해서 다뤄봅시다.
yield문의 갯수와 next()호출 횟수는 서로 같을 수도 다를 수도 있습니다.
지난 편에서 다뤘던 코드 경우,
function * generatorFunction() {
console.log('This will be executed first.');
yield 'Hello,'; // yield #1
console.log('I will be printed after the pause');
yield 'World!'; // yield #2
}
const generatorObject = generatorFunction();
console.log(generatorObject.next().value); // next() #1
console.log(generatorObject.next().value); // next() #2
console.log(generatorObject.next().value);
// This will be executed first.
// Hello,
// I will be printed after the pause
// World!
// undefined
yield와 next()호출횟수는 다음과 같다.
//yield 2번
yield 'Hello, ';
yield 'World!' ;
//next()호출횟수 2번
console.log(generatorObject.next().value);
console.log(generatorObject.next().value);
* 세번째 next()호출은 이터레이터 값이 없어 undefined를 출력했다.
또 다른 코드가 있다.
function *foo(x){
var y = x * (yield); // yield #1
return y;
}
var it = foo(6);
it.next(); // next() #1
var res = it.next(7) // next() #1
console.log(res.value)
yield와 next()호출횟수는 다음과 같다.
//yield 1번
var y = x * (yield);
//next()호출횟수 2번
it.next();
var res = it.next(7)
일반적으로는 yield 개수보다 next()호출 횟수가 더 많습니다.
이유는 다음과 같습니다.
첫번째 next()메서드는 항상 제네레이터를 실행하며 첫번째 yield키워드까지 실행하고 종료합니다.
제네레이터 함수는 호출 즉시 실행되지 않고, next()메서드를 호출해야 움직이기 때문입니다.
두번째 next()는 첫번째 yield 키워드(지점)에서 종료된 함수를 다시 실행하고 첫번째 yield 표현식을 달성 후. 그리고 두번째 yield까지 실행합니다.
세번째 next()는 두번째 yield 키워드에서 다시 실행후 표현식의 결과를 달성하고 세번째 yield까지 실행합니다.
yield와 next()메서드 호출을 간단하게 설명하면 다음과 같습니다.
#첫번째 next()
: 제네레이터 함수를 실행함, 첫번째 yield의 표현식을 넘긴다.
: 첫번째 yield 지점에서 함수를 "종료함"
#두번째 next()
: 첫번째 yield 표현식을 이룬다
: 첫번째 yield 지점에서 함수를 "다시 실행"하고,
: 두번째 yield 표현식을 넘기면서 두번째 yield지점에서 함수를 종료함
#세번째 next()
: 두번째 yield 표현식을 이룬다
: 두번째 yield 지점에서 함수를 "다시 실행"하고
: 세번째 yield 표현식을 넘기면서 세번째 yield지점에서 함수를 종료함
#첫(두,세)번째 next 는 호출하는 next()메서드의 순서입니다.
"표현식"을 이룬다는 의미는
[returnValue] = yield [expression]
위 공식에서 [expression]의 결괏값을 처리해서 [returnValue] 에 넣는다는 의미입니다.
예를 들어, 다음과 같은 식이 있을 때,
let a = yield b + c
b와c를 처리한 결과값을 a에 넣는 것을 "표현식을 이뤘다"라고 합니다.
즉, 변수 a의 값을 구하는 겁니다.
yield와 next()호출횟수 코드
:
yield와 next()호출횟수 차이설명
:
YOU DON'T KNOW JS,this와 객체 프로토타입, 비동기 성능 ,제네레이터 편
제네레이터 함수에서 주도권 - caller vs callee
: https://wonism.github.io/javascript-generator/
비동기 프로그래밍/콜백함수 부분
,
1-2. next()인자 여부 - 활용성 컨텐츠,이미지
:
http://hacks.mozilla.or.kr/2015/08/es6-in-depth-generators/
, http://hacks.mozilla.or.kr/2016/02/es6-in-depth-generators-continued/