Skip to content

Commit

Permalink
Splitted main readme in smaller chapters.
Browse files Browse the repository at this point in the history
The subdivision is temporary but should be a good starting point
  • Loading branch information
cef62 committed Feb 3, 2016
1 parent e4fdf33 commit 3deff3f
Show file tree
Hide file tree
Showing 24 changed files with 807 additions and 0 deletions.
8 changes: 8 additions & 0 deletions docs/Glossary.md
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**

27 changes: 27 additions & 0 deletions docs/README.md
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)

5 changes: 5 additions & 0 deletions docs/Troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Troubleshooting

**WIP**


64 changes: 64 additions & 0 deletions docs/advanced/ComposingSagas.md
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) )
}
}

}
```
51 changes: 51 additions & 0 deletions docs/advanced/EffectsCombinators.md
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() )
}
}
```
111 changes: 111 additions & 0 deletions docs/advanced/NonBlockingCalls.md
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>

9 changes: 9 additions & 0 deletions docs/advanced/README.md
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)

29 changes: 29 additions & 0 deletions docs/advanced/SequencingSagas.md
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.

0 comments on commit 3deff3f

Please sign in to comment.