JIGGAG

리덕스 사가 사이드 이펙트 - call

2020년 6월 7일

react-native-firebase을 사용하려고 한다. 그리고 비동기로 호출되는 requestPermissionsaga에서 제어하고자 했다. 그리고 자연스럽게 호출했는데 아무런 값을 찾지 못해 실행되지 않았다. 왜? 단지 async-awaityield로 한건데...

side effect

문서에는 call, put, all, takeEvery, takeLatest 많은 사이드 이펙트가 있으나 멱등성만 지켜지면 takeEverytakeLatest로 모든걸 할 수 있을거라 생각했다. 그럼 왜 수 많은 사이드 이펙트가 있을까? 몰라서 사용하지 못하는게 아니라 골라서 사용하길 바라는 마음으로 문서를 읽어본다.

react-native-firebase

// 기존
const request = async () => {
const messaging = await firebase.messaging();
    await messaging.requestPermission();
}

// 변경
function* workRequestPermission() {
    const messaging = yield call(firebase.messaging);

    // 1
    yield call(messaging.requestPermission);

    // 2
    yield messaging.requestPermission();

    // 3
    yield call([this, messaging.requestPermission]);
}

기존의 async-await으로 작성한 코드를 saga로 옮겨서 작성하였다. 그리고 1번 코드에서는 messaging에서 requestPermission을 찾을 수 없다는 에러메세지를 만나게 되었다. 왜 그럴까 생각하며 call에서 빼내어 2번 코드를 작성했더니 이번엔 정상적으로 동작한다. call이 수상한데 생각하며 문서를 열어보니 context라는게 눈에 보인다. 그렇다면 call에서 바라보고 있는 context는 어디일까? 똑같이 동작을 하는데 call을 사용하는 이유가 있을까? 바인딩에 대하여 많이 보던 세트 call, apply, bind 이들이 중요하게 여기던건 context이고 saga에서도 동일하게 생각해보면 context를 지정해줄 수 있기 때문에 call, apply 사이드이펙트를 사용하는 것이라 생각된다.

context

화살표 함수를 사용하면서 바인딩을 잊고 있었다. 화살표 함수는 함수 내부 this로 바인딩 해준다. 그렇다면 saga에서 requestPermission이 실행된 context는 함수 내부일까 전체일까? 사가 함수는 화살표함수가 아니다. 그럼 this는 글로벌이다. 첫번째 호출된 requestPermission에서는 글로벌 this가 자동 바인딩 되어있고 글로벌에서는 함수 내부에만 존재하는 messaging을 가져올수 없다. 두번째에는 이미 찾아둔 messaging을 가져와서 그대로 호출했다. call에 context 인자를 넣어주지 않으면 자동으로 함수 내부의 this가 바인딩 되니깐 마지막에서는 this를 명시해주었고 이는 함수 내부를 가리키며 messaging을 찾을 수 있었다.

blocking

call을 사용하는 이유에 대해 찾아보다가 blocking이라는게 눈에 들어왔다. 동시에 여러 프로세스가 실행될때, 해당 포인트를 블로킹함으로써 안정성을 주려는 것이다. 그럼 이게 사가에서 필요한 이유가 있을까? 사가는 비동기를 제어한다. 보통 api를 호출한다. 그렇다면 post api를 동시에 여러곳에서 호출면서 그와 동시에 get을 가져오고 있다면? 맨 처음 호출된 post와 마지막에 호출된 post는 과연 어떤 상태의 데이터를 바라보고 있을까? 마치 setState에서 업데이터 함수로 최신데이터를 가져와서 사용하는 느낌이랄까

선언적 vs 명령적

사가 이펙트들을 찾아서 비교해보다가 어떤 을 보게 되었다. 여기서 call로 한번 감싸서 호출하는 것을 imperativedeclarative의 차이가 있다고 설명하고 있다. firebase를 사가에서 호출하면서 call에 context를 명시해주어야만 의도대로 작동했는지 한번 더 생각해보았다. call로 감싸지 않고 yield messaging.requestPermission()하게 되면 앱이 초기화 될때 이미 실행이 되어버린다고 한다. yield call(messaging.requestPermission)는 선언만 해두고 해당 액션이 호출 될때 실행되면서 두가지 호출 방식에 context가 다르게 적용되어 있다고 생각된다.

결론

사가 이펙트를 너무 사용했다. 고민해보지 않았다. 액션은 put, 호출은 call. 그리고 무심코 사용한 이펙트로 인해 코드는 의도대로 작동하지 않았고 덕분에 한번 더 찾아볼 수 있게 되었다. 처음부터 잘 읽어보면 좋겠지만 직접 문제에 마주하는 것도 좋은 효과이지 않을까...

참고: redux-saga 가이드