var x = 1;
function foo() {
x++;
bar();
console.log("x:" , x);
}
function bar() {
x++;
}
foo();
Q. bar()가 없는데도 x++, console.log(x) 사이에서 bar()가 실행될 수 있을까?
A.
var x = 1;
function *foo() {
x++;
yield;
console.log("x:" , x);
}
function bar() {
x++;
}
foo();
var it = foo(); // 제너레이터를 제어할 이터레이터
it.next(); // 제너레이터 시작, x++; 실행
x; //2
bar(); // x++
x; //3
it.next(): // 멈췄던 곳에서 재개 console.log 실행
generator 표기법 : function foo() , function foo, function*foo => 취향대로
Generator도 함수로써 인자를 받고 반환하는 기능은 일반 함수와 같다.
function *foo(x,y) {
return x * y;
}
var it = foo(6,7);
var res = it.next();
res.value;
next()의 결괏값은 *foo()가 반환한 값을 value 프로퍼티에 저장한 객체
yield는 실행 도중에 제너레이터로부터 값을 일종의 중간 반환 값 형태로 돌려줌
메세징
yield와 next를 통해 입력/출력 메시지를 주고 받는다
function *foo(x) {
var y = x* (yield);
return y;
}
var it = foo(6);
it.next();
var res = it.next(7);
res.value;
it.next() 실행
제너레이터의 yield 중간에서 실행을 멈추고,
yield 표현식에 해당하는 결괏값을 달라고 호출부 코드에 요청
it.next(7)을 호출하면 7값이 yield 표현식의 결과값이 되도록 전달. y = 6*7가 반환
=>표현식 yield는 next 호출에 대응하여 메시지를 보낼 수 있고
next는 멈춘 yield 표현식으로 값을 전송할 수 있다.
function *foo(x) {
var y = x * (yield "hello");
return y;
}
var it = foo(6);
// 'foo()' 를 시작
it.next();
res.value; // hello
res = it.next(7);
res.value; // 42
yield는 실행 도중에 제너레이터로부터 값을 일종의 중간 반환 값 형태로 돌려줌
res.value;
it.next(7) 호출은 다시금 제너레이터가 다음에 어떤 값을 내여줄지 묻고 있는데
더 이상 이 질문에 대답할 yield문이 하나도 없다 그럼 누가 대신할까? => return 문
만약 return 문이없으면 있다고 가정하고 암시적으로 처리 (return undefined)
다중 이터레이터
같은 제너레이터의 인스턴스를 (1)동시에 여러개 실행할 수 있고 (2)인스턴스끼리 상호 작용도 가능
function *foo() {
var x = yield 2;
z++:
var y = yield(x * z);
console.log(x,y,z);
}
var z = 1;
var it1 = foo();
var it2 = foo();
var var1 = it1.next().value; // 2 <= yied 2
var var2 = it2.next().value; // 2 <= yield 2
val1 = it1.next(val2 * 10).value; // 40 <= x:20, z:2
val2 = it2.next(val1*5).value; // 600 <= x:200, z:3
it1.next(val2 /2); // y : 300
it2.next(val1/2) // y : 10
인터리빙
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3;
}
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
▲ 일반 자바스크립트 함수애서 함수의 개별문을 다른 함수에서 인터리빙하여 실행하는 것은 불가능
따라서 위의 경우 함수 순서에 따라 두가지 결과만 가능
하지만 제너레이터에서는 가능하다.
var a = 1;
var b = 2;
function *foo() {
a++;
yield;
b = b * a;
a = (yield b) + 3;
}
function *bar() {
b--;
yield;
a = (yield 8) + b;
b = a * (yield 2);
}
function step(gen) {
var it = gen();
var last;
return function() {
// 어떤 값이 'yield'되어 나오든지
// 바로 다음에 그냥 반환
last = it.next( last ).value;
};
}
// make sure to reset `a` and `b`
a = 1;
b = 2;
var s1 = step( foo );
var s2 = step( bar );
// run `*foo()` completely first
s1(); 2,2
s1(); 2,4
s1(); 7,4
// now run `*bar()`
s2(); 7,3
s2(); 7,3
s2(); 11,3
s2(); 11,22
console.log( a, b ); // 11 22
제너레이터와 이터레이터
Q. 제너레이터로 어떻게 값들을 만들어 낼까?
A.
제너레이터는 일종의 값을 생산하는 공장
이렇게 생산된 값들을 이터레이터 인터페이스의 next() 를 호출하여 한번에 하나씩 추출
제너레이터를 실행하면 이터레이터를 돌려받는다
무한수열 generator
function *something() {
var nextVal;
while (true) {
if (nextVal === undefined) {
nextVal = 1;
}
else {
nextVal = (3 * nextVal) + 6;
}
yield nextVal;
}
}
제너레이터는 이터레이터를 반환하기 때문에 for・・・of 루프에 얹을 수 있다.
for (var v of something) {
console.log( v );
// don't let the loop run forever!
if (v > 500) {
break;
}
}
// 1 9 33 105 321 969
제너레이터가 반환한 이터레이터에서 break, return등으로 for...of가 비정상 완료되면 자동으로 제너레이터를 중지하도록 신호
수동으로 제너레이터의 이터레이터를 멈추게 하려면 return () 문을 쓰면 된다.
it.return( "Hello World" ).value
제너레이터 완료 된 후에도 제너레이터에안에서 try ... finally을 통해 후처리 가능
function *something() {
try {
var nextVal;
while (true) {
if (nextVal === undefined) {
nextVal = 1;
}
else {
nextVal = (3 * nextVal) + 6;
}
yield nextVal;
}
}
// 정리 코드
finally {
console.log( "cleaning up!" );
}
}
var it = something();
for (var v of it) {
console.log( v );
// 무한 루프가 되지 않게 하라
if (v > 500) {
console.log(
// 제너레이터의 이터레이터를 마친다.
it.return( "Hello World" ).value
);
// 이터레이터는 done:true 상태로 `break` 문은 필요없다.
}
}
// 1 9 33 105 321 969
// cleaning up!
// Hello World
제너레이터를 비동기적으로 순회
제너레이터는 비동기 패턴과 무슨 상관이 있으며, 어떻게 콜백의 문제를 해결할까?
콜백
function foo(x,y,cb) {
ajax(
"http://some.url.1/?x=" + x + "&y=" + y,
cb
);
}
foo( 11, 31, function(err,text) {
if (err) {
console.error( err );
}
else {
console.log( text );
}
} );
이를 제너레이터로 표현하면
function foo(x,y) {
ajax(
"http://some.url.1/?x=" + x + "&y=" + y,
function(err,data){
if (err) {
// throw an error into `*main()`
it.throw( err );
}
else {
// resume `*main()` with received `data`
it.next( data );
}
}
);
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
var it = main();
// start it all up!
it.next();
if(err) {
// 에러를 제너레이터에 던져 yield를 멈춘다
it.throw(err);
}
▲ 이러한 제너레이터의 yield 멈춤기능은 비동기 함수의 동기적 에러 처리가 가능하게 한다.
이처럼 비동기 코드에서 난 에러를 동기적인 모양새로 처리할 수 있다는 것은 코드 가독성, 추론성면에서 매우 큰 강점
제너레이터 + 프라미스
function foo(x,y) {
return request(
"http://some.url.1/?x=" + x + "&y=" + y
);
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
var it = main();
var p = it.next().value;
// wait for the `p` promise to resolve
p.then(
function(text){
it.next( text );
},
function(err){
it.throw( err );
}
);
프로미스 동시성
서로 다른 두 데이터 소스에 데이터를 각각 요청한 후 수신한 응답을 조합해서 다시 세번째 요청
function *foo() {
var r1 = yield request( "http://some.url.1" );
var r2 = yield request( "http://some.url.2" );
var r3 = yield request(
"http://some.url.3/?v=" + r1 + "," + r2
);
console.log( r3 );
}
// use previously defined `run(..)` utility
run( foo );
▲ 순차 진행, yield는 기껏해야 코드 한곳에서 멈추게 할 수 있지만 동시에 멈추는 것은 불가능
function *foo() {
// make both requests "in parallel"
var p1 = request( "http://some.url.1" );
var p2 = request( "http://some.url.2" );
// wait until both promises resolve
var r1 = yield p1;
var r2 = yield p2;
var r3 = yield request(
"http://some.url.3/?v=" + r1 + "," + r2
);
console.log( r3 );
}
// use previously defined `run(..)` utility
run( foo );
Promise.all로도 나타낼 수 있다.
function *foo() {
// make both requests "in parallel," and
// wait until both promises resolve
var results = yield Promise.all( [
request( "http://some.url.1" ),
request( "http://some.url.2" )
] );
var r1 = results[0];
var r2 = results[1];
var r3 = yield request(
"http://some.url.3/?v=" + r1 + "," + r2
);
console.log( r3 );
}
// use previously defined `run(..)` utility
run( foo );
function *foo(val) {
if (val > 1) {
// generator recursion
val = yield *foo( val - 1 );
}
return yield request( "http://some.url/?v=" + val );
}
function *bar() {
var r1 = yield *foo( 3 );
console.log( r1 );
}
run( bar );
run(bar) 는 *bar() generator를 실행한다.
foo(3) *foo(..) 의 이터레이터 만들고 3 파라미터로 넘긴다.
3>1 이기 때문에 foo(2)는 또다른 iterator를 만들고 2를 파라미터로 넘긴다.
2>1 이기 때문에 foo(1)는 또다른 iterator를 만들고 1를 파라미터로 넘긴다.
1 > 1 은 거짓으로 request(..) 를 value1으로 호출 그리고 promise를 return 받는다.
promise가 yield되면서, *foo(2) generator instance로 돌아간다.
yield 는 이 프라미스를 다시 foo(3) 제너레이터 인스턴스로 보낸다. 또 다른 yield 는
이 프라미스를 bar() 제너레이터 인스턴스로 보내고 다시 또 다른 yield *는 run()유틸리티로 이 프라미스를
보내기를 반복하여 최초의 ajax 요청이 귀결되길 바란다.
프라미스가 귀결되면 이룸 메시지가 전송되어 bar()를 재게 yield 를 통해 foo(3) 인스턴스로 전달, foo(2) ...
일반 yield문에 이른다.
첫번째 호출 Ajax 응답값은 이제 바로 foo(3) 제너레이터 인스턴스로부터 넘겨받고 이 값을 다시 foo(2)인스턴스의 yied *
포현식 결과로 전송. 지역 변수 val에 할당
... 이런식으로 세번째 Ajax요청 실행되면 결국 bar()에서 대기 중인 yield 표현식에 도달하게 된다.
제너레이터 동시성
동시에 2개의 ajax 응답 처리기에서 ajax을 res배열에 순서 조정하여 넣기
var res = [];
function *reqData(url) {
res.push(
yield request( url )
);
}
var it1 = reqData( "http://some.url.1" );
var it2 = reqData( "http://some.url.2" );
var p1 = it1.next();
var p2 = it2.next();
p1
.then( function(data){
it1.next( data );
return p2;
} )
.then( function(data){
it2.next( data );
} );
개선1
var res = [];
function *reqData(url) {
var data = yield request( url );
// 제어권 넘김
yield;
res.push( data );
}
var it1 = reqData( "http://some.url.1" );
var it2 = reqData( "http://some.url.2" );
var p1 = it.next();
var p2 = it.next();
p1.then( function(data){
it1.next( data );
} );
p2.then( function(data){
it2.next( data );
} );
Promise.all( [p1,p2] )
.then( function(){
it1.next();
it2.next();
} );
▲ 첫 번째 인스턴스가 완전희 끝난뒤 두 번째 인스턴스 실행
그러나 여기서는 두 인스턴스가 모두 각자의 응답이 돌아오자마자 데이터를 수신하고 인스턴스 제어권을 넘긴다.
개선2
runAll ()
1.첫 번째 제너레이터는 첫 번째 AJAX 응답 프라미스를 얻고 다시 runAll() 유틸리티로 제어권을 넘겨준다.
2.두 번째 제너레이터는 AJAX 실행 후 runAll()에 제어권을 도로 갖다준다.
3.첫 번째 제너레이터 재개 후 프라미스 yield. 프라미스 귀결을 기다렸다가 같은 제너레이터 재개
p1이 귀결되면 runAll은 첫번째 제너레이터를 귀결 값으로 재개 후 res[0]에 해당값 할당
첫 번째 제너레이터가 끝나면 암시적으로 제어권 넘기이 이루어진다.
효과
두 제너레이터가 제어권 조정을 통해 res, yield된 메시지를 통해 url1, url2 두 값을 교류하면서 실질적인 통신을 한다.
// `request(..)` is a Promise-aware Ajax utility
var res = [];
runAll(
function*(){
var p1 = request( "http://some.url.1" );
// transfer control
yield;
res.push( yield p1 );
},
function*(){
var p2 = request( "http://some.url.2" );
// transfer control
yield;
res.push( yield p2 );
}
);
redux saga
redux-saga의 경우엔, 액션을 모니터링하고 있다가, 특정 액션이 발생하면 이에 따라 특정 작업을 하는 방식
=> Generator 문법을 사용
function* increaseSaga() {
yield delay(1000); // 1초를 기다립니다.
yield put(increase()); // put은 특정 액션을 디스패치 해줍니다.
}
function* decreaseSaga() {
yield delay(1000); // 1초를 기다립니다.
yield put(decrease()); // put은 특정 액션을 디스패치 해줍니다.
}
export function* counterSaga() {
yield takeEvery(INCREASE_ASYNC, increaseSaga); // 모든 INCREASE_ASYNC 액션을 처리
yield takeLatest(DECREASE_ASYNC, decreaseSaga); // 가장 마지막으로 디스패치된 DECREASE_ASYNC 액션만을 처리
}
비동기 작업을 할 때 기존 요청을 취소 처리 할 수 있습니다
특정 액션이 발생했을 때 이에 따라 다른 액션이 디스패치되게끔 하거나, 자바스크립트 코드를 실행 할 수 있습니다.
웹소켓을 사용하는 경우 Channel 이라는 기능을 사용하여 더욱 효율적으로 코드를 관리 할 수 있습니다 (참고)
제너레이터
비동기 흐름 제어를
순차적/동기적
으로 나타낼 수 있다.완전-실행(Run to Completion) 타파
Q. bar()가 없는데도 x++, console.log(x) 사이에서 bar()가 실행될 수 있을까? A.
Generator도 함수로써 인자를 받고 반환하는 기능은 일반 함수와 같다.
next()의 결괏값은 *foo()가 반환한 값을 value 프로퍼티에 저장한 객체 yield는 실행 도중에 제너레이터로부터 값을 일종의 중간 반환 값 형태로 돌려줌
메세징
yield와 next를 통해 입력/출력 메시지를 주고 받는다
y = 6*7
가 반환=>표현식 yield는 next 호출에 대응하여 메시지를 보낼 수 있고 next는 멈춘 yield 표현식으로 값을 전송할 수 있다.
yield는 실행 도중에 제너레이터로부터 값을 일종의 중간 반환 값 형태로 돌려줌
res.value;
it.next(7) 호출은 다시금 제너레이터가 다음에 어떤 값을 내여줄지 묻고 있는데 더 이상 이 질문에 대답할 yield문이 하나도 없다 그럼 누가 대신할까? => return 문 만약 return 문이없으면 있다고 가정하고 암시적으로 처리 (return undefined)
다중 이터레이터
같은 제너레이터의 인스턴스를 (1)동시에 여러개 실행할 수 있고 (2)인스턴스끼리 상호 작용도 가능
인터리빙
▲ 일반 자바스크립트 함수애서 함수의 개별문을 다른 함수에서 인터리빙하여 실행하는 것은 불가능 따라서 위의 경우 함수 순서에 따라 두가지 결과만 가능
하지만 제너레이터에서는 가능하다.
제너레이터와 이터레이터
Q. 제너레이터로 어떻게 값들을 만들어 낼까? A. 제너레이터는 일종의 값을 생산하는 공장 이렇게 생산된 값들을 이터레이터 인터페이스의 next() 를 호출하여 한번에 하나씩 추출
제너레이터를 실행하면 이터레이터를 돌려받는다
무한수열 generator
제너레이터는 이터레이터를 반환하기 때문에 for・・・of 루프에 얹을 수 있다.
cf. 클로저를 이용한 무한수열 생성 함수 상태값 보존을 위해 매번 클로저를 반환받아야함
제너레이터 멈춤
제너레이터가 반환한 이터레이터에서 break, return등으로 for...of가 비정상 완료되면 자동으로 제너레이터를 중지하도록 신호 수동으로 제너레이터의 이터레이터를 멈추게 하려면 return () 문을 쓰면 된다.
제너레이터 완료 된 후에도 제너레이터에안에서 try ... finally을 통해 후처리 가능
제너레이터를 비동기적으로 순회
제너레이터는 비동기 패턴과 무슨 상관이 있으며, 어떻게 콜백의 문제를 해결할까?
콜백
이를 제너레이터로 표현하면
▲ 이러한 제너레이터의 yield 멈춤기능은 비동기 함수의 동기적 에러 처리가 가능하게 한다.
이처럼 비동기 코드에서 난 에러를 동기적인 모양새로 처리할 수 있다는 것은 코드 가독성, 추론성면에서 매우 큰 강점
제너레이터 + 프라미스
프로미스 동시성
서로 다른 두 데이터 소스에 데이터를 각각 요청한 후 수신한 응답을 조합해서 다시 세번째 요청
▲ 순차 진행, yield는 기껏해야 코드 한곳에서 멈추게 할 수 있지만 동시에 멈추는 것은 불가능
Promise.all로도 나타낼 수 있다.
제너레이터 위임
메세지 위임
제너래이터가 아닌 일반 이터러블에도 다음과 같이 쓸 수 있다.
에러/예외
위임 재귀
제너레이터 동시성
동시에 2개의 ajax 응답 처리기에서 ajax을 res배열에 순서 조정하여 넣기
개선1
▲ 첫 번째 인스턴스가 완전희 끝난뒤 두 번째 인스턴스 실행 그러나 여기서는 두 인스턴스가 모두 각자의 응답이 돌아오자마자 데이터를 수신하고 인스턴스 제어권을 넘긴다.
개선2 runAll () 1.첫 번째 제너레이터는 첫 번째 AJAX 응답 프라미스를 얻고 다시 runAll() 유틸리티로 제어권을 넘겨준다. 2.두 번째 제너레이터는 AJAX 실행 후 runAll()에 제어권을 도로 갖다준다. 3.첫 번째 제너레이터 재개 후 프라미스 yield. 프라미스 귀결을 기다렸다가 같은 제너레이터 재개 p1이 귀결되면 runAll은 첫번째 제너레이터를 귀결 값으로 재개 후 res[0]에 해당값 할당
효과 두 제너레이터가 제어권 조정을 통해 res, yield된 메시지를 통해 url1, url2 두 값을 교류하면서 실질적인 통신을 한다.
redux saga
redux-saga의 경우엔, 액션을 모니터링하고 있다가, 특정 액션이 발생하면 이에 따라 특정 작업을 하는 방식 => Generator 문법을 사용