Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

redux-saga

Package Overview
Dependencies
Maintainers
1
Versions
74
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redux-saga - npm Package Compare versions

Comparing version 0.3.0 to 0.3.1

8

lib/proc.js

@@ -73,3 +73,3 @@ 'use strict';

var data = undefined;
return _utils.is.array(effect) ? Promise.all(effect.map(runEffect)) : _utils.is.iterator(effect) ? proc(effect, subscribe, dispatch) : (data = _io.as.take(effect)) ? runTakeEffect(data) : (data = _io.as.put(effect)) ? Promise.resolve(dispatch(data)) : (data = _io.as.race(effect)) ? runRaceEffect(data) : (data = _io.as.call(effect)) ? runCallEffect(data.fn, data.args) : (data = _io.as.cps(effect)) ? runCPSEffect(data.fn, data.args) : (data = _io.as.fork(effect)) ? runForkEffect(data.task, data.args) : (data = _io.as.join(effect)) ? runJoinEffect(data) : /* resolve anything else */Promise.resolve(effect);
return _utils.is.array(effect) ? Promise.all(effect.map(runEffect)) : _utils.is.iterator(effect) ? proc(effect, subscribe, dispatch) : (data = _io.as.take(effect)) ? runTakeEffect(data) : (data = _io.as.put(effect)) ? runPutEffect(data) : (data = _io.as.race(effect)) ? runRaceEffect(data) : (data = _io.as.call(effect)) ? runCallEffect(data.fn, data.args) : (data = _io.as.cps(effect)) ? runCPSEffect(data.fn, data.args) : (data = _io.as.fork(effect)) ? runForkEffect(data.task, data.args) : (data = _io.as.join(effect)) ? runJoinEffect(data) : /* resolve anything else */Promise.resolve(effect);
}

@@ -83,2 +83,8 @@

function runPutEffect(action) {
return Promise.resolve(1).then(function () {
return dispatch(action);
});
}
function runCallEffect(fn, args) {

@@ -85,0 +91,0 @@ return !_utils.is.generator(fn) ? Promise.resolve(fn.apply(undefined, _toConsumableArray(args))) : proc(fn.apply(undefined, _toConsumableArray(args)), subscribe, dispatch);

8

package.json
{
"name": "redux-saga",
"version": "0.3.0",
"version": "0.3.1",
"description": "Saga middleware for Redux to handle Side Effects",

@@ -12,7 +12,7 @@ "main": "lib/index.js",

"prepublish": "npm run check && npm run compile",
"build-counter": "browserify examples/counter/src/main.js -t babelify --outfile examples/counter/build.js",
"build-counter": "browserify --debug examples/counter/src/main.js -t babelify --outfile examples/counter/build.js",
"test-counter": "babel-node examples/counter/test/sagas.js | tap-spec",
"build-shop": "browserify examples/shopping-cart/src/main.js -t babelify --outfile examples/shopping-cart/build.js",
"build-shop": "browserify --debug examples/shopping-cart/src/main.js -t babelify --outfile examples/shopping-cart/build.js",
"test-shop": "babel-node examples/shopping-cart/test/sagas.js | tap-spec",
"build-async": "browserify examples/async/src/main.js -t babelify --outfile examples/async/build.js",
"build-async": "browserify --debug examples/async/src/main.js -t babelify --outfile examples/async/build.js",

@@ -19,0 +19,0 @@ "build-examples": "npm run build-counter && npm run build-shop && npm run build-async",

@@ -13,25 +13,24 @@ An alternative Side Effect model for Redux applications. Instead of dispatching thunks

>This middleware is not only about handling asynchronous flow; If all that matters is simplifying
asynchronous control flow, we could simply use async/await with some promise middleware.
>This middleware is not only about handling asynchronous flow. If all what matters is simplifying
asynchronous control flow, one could simply use async/await functions with some promise middleware.
What the The middleware provides is
What this middleware proposes is
- A composable abstraction **Effect**: Waiting for an action, triggering State updates (by dispatching
actions to the store), calling a remote service are all different forms of Effects. A Saga compose those
- A composable abstraction **Effect**: waiting for an action, triggering State updates (by dispatching
actions to the store), calling a remote service are all different forms of Effects. A Saga composes those
Effects using familiar control flow constructs (if, while, for, try/catch).
- The Saga is itself an Effect that can be combined with other Effects using Effect combinators (parallel or
race) and called from inside other Sagas, which all the power of Subroutines and
[Structured Programming](https://en.wikipedia.org/wiki/Structured_programming)
- The Saga is itself an Effect. It can be combined with other Effects using combinators.
It can also be called from inside other Sagas, providing the full power of Subroutines and
[Structured Programming](https://en.wikipedia.org/wiki/Structured_programming)
- Effects may be yielded declaratively, i.e. you yield a description of the Effect that
will executed by the middleware. This makes your operational logic inside Generators fully testable.
- Effects may be yielded declaratively. You yield a description of the Effect which will be
executed by the middleware. This makes your operational logic inside Generators fully testable.
- You can implement complex operations with logic that spans across multiple actions (e.g. User onBoarding, Wizard
dialogs, complex Game rules ...), which are not trivial to express using redux-thunk or other effects
middlewares that can be only fired from Action Creators.
dialogs, complex Game rules ...), which are not trivial to express using other effects middlewares.
- [Getting started](#getting-started)
- [How is this different from other asynchronous middlewares](#how-does-it-works)
- [How is this different from other asynchronous middlewares](#how-is-this-different-from-the-others)
- [Declarative Effects](#declarative-effects)

@@ -42,3 +41,3 @@ - [Error handling](#error-handling)

- [Composing Sagas](#composing-sagas)
- [Concurrent tasks tasks with fork/join ](#concurrent-tasks-with-forkjoin)
- [Non blocking calls with fork/join](#non-blocking-calls-with-forkjoin)
- [Building from sources](#building-from-sources)

@@ -55,4 +54,5 @@

```javascript
import { take, call } from 'redux-saga'
import { take, put } from 'redux-saga'
// sagas/index.js
function* incrementAsync() {

@@ -65,3 +65,4 @@

// call delay : Number -> Promise
// delay is a sample function
// return a Promise that resolves after (ms) milliseconds
yield delay(1000)

@@ -94,18 +95,18 @@

#How is this different from other asynchronous middlewares
#How is this different from the others
In the above example we created an `incrementAsync` Saga. The call `yield take(action)` is a
typical illustration on how Saga works.
In the previous example we created an `incrementAsync` Saga. The call `yield take(action)` is a
typical illustration of how Sagas work.
Typically, actual middlewares handle some Effects triggered by an Action Creator. for example,
redux-thunk handles *thunks* by calling the triggered thunk providing it with `(getState, dispatch)`,
redux-promise handles Promises by dispatching their resolved values, redux-gen handles generators by
dispatching all yielded actions to the store. The common thing is that all those middlewares shares the
Typically, actual middlewares handle some Effect form triggered by an Action Creator. For example,
redux-thunk handles *thunks* by calling them with `(getState, dispatch)` as arguments,
redux-promise handles Promises by dispatching their resolved values. redux-gen handles generators by
dispatching all yielded actions to the store. The common thing that all those middlewares share is the
same 'call on each action' pattern. They will be called again and again each time an action happens,
i.e. they are scoped by the *root action* which triggered them.
i.e. they are *scoped* by the *root action* that triggered them.
Sagas works differently, they are not fired from within Action Creators but are started with your
application and choose what user actions to watch for. They are a sort of daemon tasks that run in
Sagas work differently, they are not fired from within Action Creators but are started with your
application and choose what user actions to watch. They are like daemon tasks that run in
the background and choose their own logic of progression. In the example above, `incrementAsync` *pulls*
the `INCREMENT_ASYNC` action, the `yield take(action)` is a *blocking call*, which means the Saga
the `INCREMENT_ASYNC` action using `yield take(...)`. This is a *blocking call*, which means the Saga
will not progress until it receives a matching action.

@@ -115,18 +116,17 @@

returns a Promise that will be resolved after 1 second. Again, this is a blocking call, so the Saga
will wait for 1 second before continuing on (a better way is `io.call(delay, 1000)`, see section
on declarative Effects).
will wait for 1 second before continuing on (a better way is `call(delay, 1000)`, as we'll see in
the section on declarative Effects).
After the 1 second delay, the Saga dispatches an `INCREMENT_COUNTER` action using the `put(action)`
After the delay, the Saga dispatches an `INCREMENT_COUNTER` action using the `put(action)`
function. Here also, the Saga will wait for the dispatch result. If the dispatch call returns
a normal value, the Saga resumes *immediately*, but if the result value is a Promise then the
a normal value, the Saga resumes *immediately* (asap), but if the result value is a Promise then the
Saga will wait until the Promise is resolved (or rejected).
To generalize, waiting for the next action (`yield take(MY_ACTION)`), for a the future result of
a function (`yield delay(1000)`) or for the result of a dispatch (`yield put(myAction())`) are all
different expressions of the same concept: *yielding a side effect*. Basically this is how Sagas
work.
To generalize, waiting for a future action (`yield take(MY_ACTION)`), waiting for the future result of
a function call (`yield delay(1000)`) or waiting for the result of a dispatch (`yield put(myAction())`)
all are the same concept. In all cases, we are yielding some form of side effects.
Note also how `incrementAsync` uses an infinite loop `while(true)` which means it will stay alive
for all the application lifetime. You can also create Sagas that last only for a limited amount of
time. For example, the following Saga will wait for the first 3 `INCREMENT_COUNTER` actions,
time. For example, the following Saga waits for the first 3 `INCREMENT_COUNTER` actions,
triggers a `showCongratulation()` action and then finishes.

@@ -151,2 +151,3 @@

// fetch is a sample function
// returns a Promise that will resolve with the GET response

@@ -160,4 +161,4 @@ const products = yield fetch('/products')

In the example above, `fetch('/url')` returns a Promise that will resolve with the GET response. So the 'fetch effect' will be
executed immediately . Simple and idiomatic but ...
In the example above, `fetch('/products')` returns a Promise that will resolve with the GET response.
So the 'fetch effect' will be executed immediately . Simple and idiomatic but ...

@@ -172,7 +173,7 @@ Suppose we want to test generator above

We want to check the result of the first value yielded by the generator, which is in our case the result of running
`fetch('/products')`. Executing the real service during tests is not a viable nor a practical approach, so we have to *mock* the
fetch service, i.e. we'll have to replace the real `fetch` method with a fake one which doesn't actually run the
GET request but only checks that we've called `fetch` with the right arguments (`'/products'` in our case).
`fetch('/products')`. Executing the real service during tests is not a viable nor a practical approach, so we have to
*mock* the fetch service, i.e. we'll have to replace the real `fetch` method with a fake one which doesn't actually
run the GET request but only checks that we've called `fetch` with the right arguments (`'/products'` in our case).
Mocks makes testing more difficult and less reliable. On the other hand, functions which returns simple values are
Mocks make testing more difficult and less reliable. On the other hand, functions that simply return values are
easier to test, we can use a simple `equal()` to check the result.This is the way to write the most reliable tests.

@@ -204,3 +205,3 @@

Redux you use action creators to create a plain object describing the action that will get executed by the Store,
`io.call` creates a plain object describing the function call. The redux-saga middleware takes care of executing
`call` creates a plain object describing the function call. The redux-saga middleware takes care of executing
the function call and resuming the generator with the resolved response.

@@ -225,3 +226,3 @@

The `call` method is well suited for functions which return Promise results. Another function
The `call` method is well suited for functions that return Promise results. Another function
`cps` can be used to handle Node style functions (e.g. `fn(...args, callback)` where `callback`

@@ -300,6 +301,6 @@ is of the form `(error, result) => ()`). For example

Because the 2nd Effect will not get executed until the first call resolves. Instead we have to write
Because the 2nd effect will not get executed until the first call resolves. Instead we have to write
```javascript
import { call, race } from 'redux-saga'
import { call } from 'redux-saga'

@@ -313,6 +314,6 @@ // correct, effects will get executed in parallel

When we yield an array of effects, the Generator is blocked until all the effects are resolved (or as soon as
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 when to start multiple tasks in parallel, we don't want to wait for all of them, we just need
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

@@ -325,6 +326,8 @@ triggering a race between multiple effects.

```javascript
import { race, take, put } from 'redux-saga'
function* fetchPostsWithTimeout() {
while( yield take(FETCH_POSTS) ) {
// starts a race between 2 effects
const {posts, timeout} = race({
const {posts, timeout} = yield race({
posts : call(fetchApi, '/posts'),

@@ -374,17 +377,14 @@ timeout : call(delay, 1000)

While using `yield*` provides an idiomatic way of compositing Sagas. The approach has some limits:
While using `yield*` provides an idiomatic way of composing Sagas. This approach has some limits:
- You'll likely want to test nested generators separately. This leads to some duplication because when
testing the main generator you'll have to iterate again on all the nested generators. This
can be achieved by reusing the tests for sub-generators inside the main test but this introduce
more boilerplate inside your tests besides the overhead of the repeated execution. All we want is to
test that the main task yields to the correct subtask not actually execute it.
- You'll likely want to test nested generators separately. This leads to some duplication in the test
code as well as an 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, you can only
yield* to one generator at a time. But there can be use cases when you want to launch multiple
tasks in parallel, and wait for them all to terminate in order to progress.
yield* to one generator at a time.
You can simply use `yield subtask()`. When yielding a call to a generator, the Saga
will wait for the generator to terminate before progressing. And resumes then with the
returned value (or throws if an error propagates from the subtask).
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 resumes
with the returned value (or throws if an error propagates from the subtask).

@@ -406,5 +406,14 @@

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 more different than yielding other effects (future actions, timeouts ...).
It means you can combine those Sagas with all the other types of Effects using the effect combinators
(`[...effects]` and `race({...})`).
It means you can combine those Sagas with all the other types using the effect combinators.

@@ -426,3 +435,3 @@ For example you may want the user finish some game in a limited amount of time

finished = true
put( showScore(score) )
yield put( showScore(score) )
}

@@ -434,19 +443,7 @@ }

Or you may want to start multiple Sagas in parallel to monitor user actions and congratulate
the user when he accomplished all the desired tasks.
#Non blocking calls with fork/join
```javascript
function* watchTask1() {...}
function* watchTask2() {...}
the `yield` statement causes the generator to pause until the yielded effect resolves or rejects.
If you look closely at this example
function* game(getState) {
yield [ call(watchTask1), call(watchTask2)]
yield put( showCongratulation() )
}
```
#Concurrent tasks with fork/join
the `yield` statement causes the generator to pause until the yielded effect has resolved. If you look
closely at this example
```javascript

@@ -456,3 +453,3 @@ function* watchFetch() {

yield put( actions.requestPosts() )
const posts = yield call(fetchApi, '/posts') // will be blocked here
const posts = yield call(fetchApi, '/posts') // blocking call
yield put( actions.receivePosts(posts) )

@@ -463,9 +460,9 @@ }

the `watchFetch` generator will wait until `yield call(fetchPosts)` terminates. Imagine that the
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 fecthes) then there is no issue, because we know that no `FETCH_POSTS` action
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 if the application allows the user to click on `Refresh` without waiting for the current request
to terminate ? What will happen ?
But what happens if the application allows the user to click on `Refresh` without waiting for the
current request to terminate ?

@@ -487,10 +484,10 @@ The following example illustrates a possible sequence of the events

When `watchFetch` is blocked on the `fetchApi` call (waiting for the server response), all `FETCH_POSTS`
occurring in between the call and the response are missed.
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 call, we can use the `fork` function. A possible rewrite of the previous example
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 } from 'redux-saga'
import { fork, call, take, put } from 'redux-saga'

@@ -505,3 +502,3 @@ function* fetchPosts() {

while ( yield take(FETCH_POSTS) ) {
yield fork(fetchPosts) // will not block here
yield fork(fetchPosts) // non blocking call
}

@@ -519,12 +516,14 @@ }

The result of `yield fork(api)` will be a *Task descriptor*. To get the
result of a forked Task in a later time, we use the `join` function
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'
// non blocking call
const task = yield fork(...)
const task = yield fork(subtask, ...args)
// ... later
// now a blocking call, will resolve the outcome of task
const result = yield join(...)
// now a blocking call, will resume with the outcome of task
const result = yield join(task)
```

@@ -549,3 +548,3 @@

There are 2 examples ported from the Redux repos
There are 3 examples ported from the Redux repos

@@ -569,1 +568,9 @@ Counter example

```
async example
```
// build the example
npm run build-async
//sorry, no tests yet
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc