yield

비동기 처리 (Asynchronous processing model)

 

 

javascipt에서 지원하는 함수 중에 yield라는 함수가 있다. 글쓴이는 javascript를 잘 모르는 상태에서 react를 개발을 시작했었고, 처음으로 yield라는 함수를 마주하게 되었다. 그 당시 이 함수가 react에서 제공하는 특수한 함수인 줄 알았다. 하지만, yield는 react를 개발하면서 처음 봤기에 잘 못알고 있었으며, javascript의 ES6부터 사용가능한 함수 중 하나라는 것을 알았다. yield 키워드는 javascript의 비동기 처리를 위해 만들어진 것으로, 개발을 할 때 자주사용하게 되는 함수이다. 특히 react 개발을 하고 있다면 분명 사용하는 키워드일 것이다. 이번 포스팅에서는 이러한 yield 키워드가 javascript에서 사용되는 상황react에서 사용되는 상황을 보고 어떻게 다른 형태로 사용되는지 비교해보려 한다.

 

 

javascript의 yield 키워드만을 사용하는 경우

[ yield 만 사용하는 경우 ]

function*으로 함수를 정의하고, 이 함수 내에 포함되어 있는 property 중에 next()를 차례로 호출함으로써, 함수 내에 코딩해둔 yield를 차례로 실행시키는 것이고, 아래 3가지 키워드를 사용해서 코드가 만들어진다.

 

function* : function 뒤에 astrok(*)를 붙이면, 이를 generator function이라고 불리는 함수형태가 된다. 이 함수는 비동기처리를 할 수 있는 함수로서 내부에서 yield라는 키워드를 사용해서 코딩을 하면, 해당 라인까지만 코드를 실행하고 뒷부분은 다시 실행을 요청할 때까지 실행하지 않게함으로써 마치 함수가 실행 중에 특정부분에서 일시 정지된 것처럼 동작함으로써, 비동기 처리를 하도록 만들 수가 있다.

yield : generator function 안에 yield 키워드를 사용한 곳들을 기준으로 코드가 잘려서 실행하게 된다고 볼 수 있다. 만약에, yield문을 만날때까지 특정 처리를 수행했다면, 함수는 잠깐 잘린 상태로 정지해있다가 다시 이 함수로 접근했을 때(=next함수를 호출했을 때) 멈췄던 부분부터 실행을 이어서 하고 다음 yield문을 만날때까지 쭈욱 실행이 되는 것이다. 이러한 구조로 인해 yield를 기준으로 처리가 나눠진 곳들을 실행시키는 명령을 외부로 넘기는 형태가 만들어지게 되는 것이다. 따라서, generator function을 호출하는 외부 함수(=caller)에서 next()를 실행시킬 때마다 yield로 나눠진 곳들이 하나씩 실행하게 되는 것이다.

next() : generator function에서 반환한 데이터를 generator라고 부르는데, generator의 next 함수를 실행하면 { value, done } 이라는 데이터를 반환한다. 참고로, next()를 실행하는 이유는 generator는 반복구조인 iterator를 사용하는 구조이기 때문이다.

 

아래는 yield를 사용해서 비동기 처리를 하는 call()이라는 함수를 만들어보았다. 참고로, 함수를 호출할 때는 next()를 사용해도 되지만, 배열을 반복하는 for..of문법을 사용해서 call() 함수 내에 있는 yield를 차례로 호출시킬 수도 있다.

 

sample code

function* call() { 
    console.log('execute joker 1'); 
    yield 10; 
    console.log('execute joker 2'); 
    yield (20 + 20); 
    console.log('execute joker 3'); 
    yield (30 * 3); 
} 

// call style 1
let gen = call(); 
console.log(gen.next()); 
console.log(gen.next()); 
console.log(gen.next()); 
console.log(gen.next());

// call style 2
for(let result of call()) { 
    console.log(result); 
}

 

sample code output

execute joker 1
{
    value:10,
    done:false
}
execute joker 2
{
    value:40,
    done:false
}
execute joker 3
{
    value:90,
    done:false
}
{
    value:undefined,
    done:true
}
execute joker 1
10
execute joker 2
40
execute joker 3
90

 

 

javascript의 yield와 (react) redux-saga의 effects를 함께 사용하는 경우

[ yield와 redux-saga/effects를 같이 사용하는 경우 ]

function*으로 함수를 정의하고, 이 함수를 호출하는 행위는 redux-saga/effects에 위임시키고, redux에서 제공하는 액션(action)을 사용해서 함수 내에 코딩해둔 yield를 차례로 실행시키는 것이다. 이때, redux에서 함수 내의 yield를 차례로 알아서 실행시켜주기 때문에 앞에서 순수 javascript만으로 개발했을 때처럼 next()를 차례로 호출시킬 필요는 없다. 참고로, redux는 앞에서 설명한 ES6의 genrator function을 기반으로 구현되었다. 아래 2가지 키워드와 redux-saga/effects 함수들을 사용해서 코드가 만들어지게 된다.

 

function* : ES6 javascript의 문법으로 앞 설명과 동일

yield : ES6 javascript의 문법으로 앞 설명과 동일

redux-saga/effects 함수 : react 개발을 할 때, redux-saga/effects 에서 제공되는 함수들(ex, call, put)을 사용하는 경우가 종종 있다. 이렇게 react 개발시 redux 라이브러리들을 사용하게 되면, 앞에서 설명했던 것처럼 function* 함수를 호출할 때 next()를 통해서 호출하는 것이 아닌, 액션(action)이라는 것을 통해서 호출하게 된다. 그리고 react에서는 액션을 통해 function* 함수를 한번 호출하고 나면 내부에 있는 여러 yield 함수들을 차례로 호출을 해준다는 점이다. 이러한 점이 개발자로 하여금 비동기처리에 대한 코드를 더욱 쉽게 만들어 준다.

 

참고로 redux-saga/effects 함수로는 all, call, put, takeLatest 등이 있으며, all은redux에서 (비동기 처리가 필요한) 함수들을 배열 형태로 넣어서 동시에 병행으로 처리를 수행하는 함수이고, call은 redux에서 (비동기 처리가 필요한) 함수를 실행하는 함수이다. 보통 ajax call, http, delay 등의 함수를 처리할 때 사용한다. put은 redux의 액션(action)을 디스패치(dispatch)하는 함수이고, takeLatest는 redux의 (기존에 진행중이던 작업이 있다면 취소하고) 가장 마지막으로 실행된 작업을 수행하는 함수이다.

 

아래는 yield를 사용하긴 하지만, react를 사용할 때는 어떠한 형태가 나오는지 만들어보았다. 아래 코드는 전체 코드의 일부분을 발췌한 것이기 때문에 동작을 하려면 다른 코드들이 더 있어야 한다. yield를 사용할 때의 상황이 react개발할때는 javascript만 사용할 때와 어떻게 다른지 살펴보기 위함이기 때문에 아래 코드만으로 이해가 가능할 것이라고 생각된다. 참고로, react 개발시 비동기 처리에 대한 다양한 기능들을 지원해주는 라이브러리인 redux-saga를 정말 많이 사용한다.

 

sample code

// using redux saga effects in react
import { createAction } from 'redux-actions';
import { delay } from 'redux-saga'
import { all, call, put, takeLatest } from 'redux-saga/effects';

// react-specific (2. 사용자가 react-component(=UI 부분코드)로 출력되는 특정 버튼을 누르면 login 액션을 호출)
const LOGIN = 'LOGIN';
export const login = createAction(LOGIN);

// javascript generator function (3. login 액션에 대한 수행을 하는 담당하는 함수)
function* loginSaga(data) {

    // check 1
    yield delay(3000);
    console.log('execute joker 1');

     // check 2
    yield delay(2000);
    console.log('execute joker 2');

    // check 3
    yield delay(5000);
    return yield call(httpRequest, { data });
}

// react-specific (1. redux-saga로 미리 사용할 액션을 등록해두어야 login 액션을 받을 수 있음)
export function* actionLogin() {
  yield takeLatest(LOGIN, loginSaga);
}

 

sample code output

// check 1에서 3초간 일시정지 후.. 다음 라인부터 실행한다.
execute joker 1

// check 2에서 2초간 일시정지 후.. 다음 라인부터 실행한다.
execute joker 2

// check 3에서 5초간 일시정지 후.. 다음 라인부터 실행한다.
// httpRequest함수에 대한 결과를 리턴한다.

 

 

참고

Mozila Generator.prototype.next() link

Mozila yield* link