-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Splitted main readme in smaller chapters.
The subdivision is temporary but should be a good starting point
- Loading branch information
Showing
24 changed files
with
807 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Glossary | ||
|
||
This is a glossary of the core terms in Redux Saga. | ||
|
||
--- | ||
|
||
**WIP** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
## Table of Contents | ||
|
||
* [Read Me](/README.md) | ||
* [Introduction](/docs/introduction/README.md) | ||
* [Basic Concepts](/docs/introduction/Concept.md) | ||
* [Saga Background](/docs/introduction/SagaBackground.md) | ||
* [Basics](/docs/basics/README.md) | ||
* [Getting Started](/docs/basics/GettingStarted.md) | ||
* [Waiting for future actions](/docs/basics/FutureActions.md) | ||
* [Dispatching actions](/docs/basics/DispatchingActions.md) | ||
* [Effect](/docs/basics/Effect.md) | ||
* [Declarative Effects](/docs/basics/DeclarativeEffects.md) | ||
* [Error handling](/docs/basics/ErrorHandling.md) | ||
* [Advanced](/docs/advanced/README.md) | ||
* [Effect Combinators](/docs/advanced/EffectsCombinators.md) | ||
* [Sequencing Sagas](/docs/advanced/SequencingSagas.md) | ||
* [Composing Sagas](/docs/advanced/ComposingSagas.md) | ||
* [Non blocking calls](/docs/advanced/NonBlockingCalls.md) | ||
* [Task cancellation](/docs/advanced/TaskCancellation.md) | ||
* [Using runSaga](/docs/advanced/UsingRunSaga.md) | ||
* [Contribute](/docs/contribute/README.md) | ||
* [Building examples](/docs/contribute/BuildingExamples.md) | ||
* [UMD build](/docs/contribute/UmdBuild.md) | ||
* [Troubleshooting](/docs/Troubleshooting.md) | ||
* [Glossary](/docs/Glossary.md) | ||
* [API Reference](/docs/api/README.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Troubleshooting | ||
|
||
**WIP** | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Composing Sagas | ||
|
||
While using `yield*` provides an idiomatic way of composing Sagas, this approach has some limitations: | ||
|
||
- You'll likely want to test nested generators separately. This leads to some duplication in the test | ||
code as well as the overhead of the duplicated execution. We don't want to execute a nested generator | ||
but only make sure the call to it was issued with the right argument. | ||
|
||
- More importantly, `yield*` allows only for sequential composition of tasks, so you can only | ||
yield* to one generator at a time. | ||
|
||
You can simply use `yield` to start one or more subtasks in parallel. When yielding a call to a | ||
generator, the Saga will wait for the generator to terminate before progressing, then resume | ||
with the returned value (or throws if an error propagates from the subtask). | ||
|
||
|
||
```javascript | ||
function* fetchPosts() { | ||
yield put( actions.requestPosts() ) | ||
const products = yield call(fetchApi, '/products') | ||
yield put( actions.receivePosts(products) ) | ||
} | ||
|
||
function* watchFetch() { | ||
while ( yield take(FETCH_POSTS) ) { | ||
yield call(fetchPosts) // waits for the fetchPosts task to terminate | ||
} | ||
} | ||
``` | ||
|
||
Yielding to an array of nested generators will start all the sub-generators in parallel and wait | ||
for them to finish. Then resume with all the results | ||
|
||
```javascript | ||
function* mainSaga(getState) { | ||
const results = yield [ call(task1), call(task2), ...] | ||
yield put( showResults(results) ) | ||
} | ||
``` | ||
|
||
In fact, yielding Sagas is no different than yielding other effects (future actions, timeouts, etc). | ||
This means you can combine those Sagas with all the other types using the effect combinators. | ||
|
||
For example you may want the user to finish some game in a limited amount of time: | ||
|
||
```javascript | ||
function* game(getState) { | ||
|
||
let finished | ||
while(!finished) { | ||
// has to finish in 60 seconds | ||
const {score, timeout} = yield race({ | ||
score : call( play, getState), | ||
timeout : call(delay, 60000) | ||
}) | ||
|
||
if(!timeout) { | ||
finished = true | ||
yield put( showScore(score) ) | ||
} | ||
} | ||
|
||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#Effect Combinators | ||
|
||
The `yield` statement is great for representing asynchronous control flow in a simple and linear | ||
style, but we also need to do things in parallel. We can't simply write: | ||
|
||
```javascript | ||
// Wrong, effects will be executed in sequence | ||
const users = yield call(fetch, '/users'), | ||
repose = yield call(fetch, '/repose') | ||
``` | ||
|
||
Because the 2nd effect will not get executed until the first call resolves. Instead we have to write: | ||
|
||
```javascript | ||
import { call } from 'redux-saga' | ||
|
||
// correct, effects will get executed in parallel | ||
const [users, repose] = yield [ | ||
call(fetch, '/users'), | ||
call(fetch, '/repose') | ||
] | ||
``` | ||
|
||
When we yield an array of effects, the generator is blocked until all the effects are resolved (or as soon as | ||
one is rejected, just like how `Promise.all` behaves). | ||
|
||
Sometimes we start multiple tasks in parallel but we don't want to wait for all of them, we just need | ||
to get the *winner*: the first one that resolves (or rejects). The `race` function offers a way of | ||
triggering a race between multiple effects. | ||
|
||
The following sample shows a Saga that triggers a remote fetch request, and constrain the response with a | ||
1 second timeout. | ||
|
||
```javascript | ||
import { race, take, put } from 'redux-saga' | ||
|
||
function* fetchPostsWithTimeout() { | ||
while( yield take(FETCH_POSTS) ) { | ||
// starts a race between 2 effects | ||
const {posts, timeout} = yield race({ | ||
posts : call(fetchApi, '/posts'), | ||
timeout : call(delay, 1000) | ||
}) | ||
|
||
if(posts) | ||
put( actions.receivePosts(posts) ) | ||
else | ||
put( actions.timeoutError() ) | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# 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: | ||
|
||
```javascript | ||
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: | ||
|
||
```javascript | ||
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. | ||
|
||
```javascript | ||
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 | ||
|
||
```javascript | ||
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 | ||
|
||
<table> | ||
<tr> | ||
<th>method</th> | ||
<th>return value</th> | ||
</tr> | ||
<tr> | ||
<td>task.isRunning()</td> | ||
<td>true if the task hasn't yet returned or throwed an error</td> | ||
</tr> | ||
<tr> | ||
<td>task.result()</td> | ||
<td>task return value. `undefined` if task is still running</td> | ||
</tr> | ||
<tr> | ||
<td>task.error()</td> | ||
<td>task thrown error. `undefined` if task is still running</td> | ||
</tr> | ||
<tr> | ||
<td>task.done</td> | ||
<td> | ||
a Promise which is either | ||
<ul> | ||
<li>resolved with task's return value</li> | ||
<li>rejected with task's thrown error</li> | ||
</ul> | ||
</td> | ||
</tr> | ||
</table> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Advanced | ||
|
||
* [Effect Combinators](EffectsCombinators.md) | ||
* [Sequencing Sagas](SequencingSagas.md) | ||
* [Composing Sagas](ComposingSagas.md) | ||
* [Non blocking calls](NonBlockingCalls.md) | ||
* [Task cancellation](TaskCancellation.md) | ||
* [Using runSaga](UsingRunSaga.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Sequencing Sagas via `yield*` | ||
|
||
You can use the builtin `yield*` operator to compose multiple sagas in a sequential way. | ||
This allows you to sequence your *macro-tasks* in a simple procedural style. | ||
|
||
```javascript | ||
function* playLevelOne(getState) { ... } | ||
|
||
function* playLevelTwo(getState) { ... } | ||
|
||
function* playLevelThree(getState) { ... } | ||
|
||
function* game(getState) { | ||
|
||
const score1 = yield* playLevelOne(getState) | ||
put(showScore(score1)) | ||
|
||
const score2 = yield* playLevelTwo(getState) | ||
put(showScore(score2)) | ||
|
||
const score3 = yield* playLevelThree(getState) | ||
put(showScore(score3)) | ||
|
||
} | ||
``` | ||
|
||
Note that using `yield*` will cause the JavaScript runtime to *spread* the whole sequence. | ||
The resulting iterator (from `game()`) will yield all values from the nested | ||
iterators. A more powerful alternative is to use the more generic middleware composition mechanism. |
Oops, something went wrong.