What is the point of watchers in redux-saga?
Asked Answered
H

1

7

I just got into redux-saga and I'm a bit confused on Watchers and Generators

Take for example this code below, that is the entry-point of my sagas file

function* employeesSaga() {
    yield all([
        takeEvery(getType(employeeActions.login.request), handleLoginRequest),
        takeEvery(getType(employeeActions.verifyLogin.request), handleVerifyLoginRequest),
        takeEvery(getType(employeeActions.logout), handleLogout)
    ]);
}

I am directly wiring each redux call to the corresponding handler.

But some people use watchers, and then they call the handler in that generator. What is the purpose of doing that? Should I use that pattern?

Also, I noticed some people wrap their entire handler with a while(true), is that necessary? Because my code works fine without that too...

Hootenanny answered 16/11, 2018 at 6:1 Comment(0)
R
13

On the first question

It may be just a matter of readability.

Watchers aren't a real "pattern", they just make your code more explicit about its intentions:

function* watchLoginRequest() {
    yield takeEvery(getType(employeeActions.login.request), handleLoginRequest)
}

function* watchVerifyLoginRequest() {
    yield takeEvery(getType(employeeActions.verifyLogin.request), handleVerifyLoginRequest)
}

function* watchLogout() {
    yield takeEvery(getType(employeeActions.logout), handleLogout)
}

function* startWatchers() {
    yield call(watchLoginRequest)
    yield call(watchVerifyLoginRequest)
    yield call(watchLogout)
}

function* employeesSaga() {
    yield call(startWatchers)
}

Second question

Probably you're talking about this kind of flow:

function* watchLoginRequest() {
    while (true) {
        const action = yield take(getType(employeeActions.login.request))
        yield call(handleLoginRequest, action)
    }
}

The difference:

  • The while(true)-take-call flow does not allow executing two instances of a single handler concurrently. It takes one action, then blocks on the call, until the handleLoginRequest() has finished. If the user clicks the login button before the handler has completed, the correnponding actions are missed.

  • The takeEvery() flow does allow concurrent handler execution. This might not be what you want.

Redux-saga docs tell how takeEvery() is implemented under the hood:

const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
  while (true) {
    const action = yield take(patternOrChannel)
    yield fork(saga, ...args.concat(action))
  }
})

You see, takeEvery() is non-blocking itself (a fork), and it executes the handler in a non-blocking way (a fork).

A third option

There is also takeLatest(), which does allow concurrent handler execution, but if there is a previous instance of a handler executing, it cancels it. Redux-saga docs provide its internal implementation, too.

The while(true)-take-call is the best as a login flow, I think. You probably do not want concurrent logins. If you block concurrent logins on UI level, though, these flows are equivalent, but while(true)-take-call is the most explicit and readable.

Ryeland answered 16/11, 2018 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.