Non blocking calls with fork/join
the yield
statement causes the generator to pause until the yielded effect resolves or rejects.
If you look closely at this example:
function* watchFetch() {
while ( yield take(FETCH_POSTS) ) {
yield put( actions.requestPosts() )
const posts = yield call(fetchApi, '/posts') // blocking call
yield put( actions.receivePosts(posts) )
}
}
the watchFetch
generator will wait until yield call(fetchApi, '/posts')
terminates. Imagine that the FETCH_POSTS
action is fired from a Refresh
button. If our application disables the button between each fetch (no concurrent fetches) then there is no issue, because we know that no FETCH_POSTS
action will occur until we get the response from the fetchApi
call.
But what happens if the application allows the user to click on Refresh
without waiting for the current request to terminate?
The following example illustrates a possible sequence of the events
UI watchFetch
--------------------------------------------------------
FETCH_POSTS.....................call fetchApi........... waiting to resolve
........................................................
........................................................
FETCH_POSTS............................................. missed
........................................................
FETCH_POSTS............................................. missed
................................fetchApi returned.......
........................................................
When watchFetch
is blocked on the fetchApi
call, all FETCH_POSTS
occurring in between the call and the response are missed.
To express non-blocking calls, we can use the fork
function. A possible rewrite of the previous example with fork
can be:
import { fork, call, take, put } from 'redux-saga'
function* fetchPosts() {
yield put( actions.requestPosts() )
const posts = yield call(fetchApi, '/posts')
yield put( actions.receivePosts(posts) )
}
function* watchFetch() {
while ( yield take(FETCH_POSTS) ) {
yield fork(fetchPosts) // non blocking call
}
}
fork
, just like call
, accepts function/generator calls.
yield fork(func, ...args) // simple async functions (...) -> Promise
yield fork(generator, ...args) // Generator functions
The result of yield fork(api)
is a Task descriptor. To get the result of a forked Task
in a later time, we use the join
function
import { fork, join } from 'redux-saga'
function* child() { ... }
function *parent() {
// non blocking call
const task = yield fork(subtask, ...args)
// ... later
// now a blocking call, will resume with the outcome of task
const result = yield join(task)
}
the task object exposes some useful methods
method | return value |
---|---|
task.isRunning() | true if the task hasn't yet returned or throwed an error |
task.result() | task return value. `undefined` if task is still running |
task.error() | task thrown error. `undefined` if task is still running |
task.done |
a Promise which is either
|