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.3 to 0.4.0

dist/redux-saga.js

92

lib/index.js

@@ -6,4 +6,31 @@ 'use strict';

});
exports.join = exports.fork = exports.cps = exports.call = exports.race = exports.put = exports.take = undefined;
exports.storeIO = exports.runSaga = exports.as = exports.cancel = exports.join = exports.fork = exports.cps = exports.call = exports.race = exports.put = exports.take = exports.MANUAL_CANCEL = exports.PARALLEL_AUTO_CANCEL = exports.RACE_AUTO_CANCEL = exports.SagaCancellationException = undefined;
var _proc = require('./proc');
Object.defineProperty(exports, 'SagaCancellationException', {
enumerable: true,
get: function get() {
return _proc.SagaCancellationException;
}
});
Object.defineProperty(exports, 'RACE_AUTO_CANCEL', {
enumerable: true,
get: function get() {
return _proc.RACE_AUTO_CANCEL;
}
});
Object.defineProperty(exports, 'PARALLEL_AUTO_CANCEL', {
enumerable: true,
get: function get() {
return _proc.PARALLEL_AUTO_CANCEL;
}
});
Object.defineProperty(exports, 'MANUAL_CANCEL', {
enumerable: true,
get: function get() {
return _proc.MANUAL_CANCEL;
}
});
var _io = require('./io');

@@ -53,43 +80,36 @@

});
Object.defineProperty(exports, 'cancel', {
enumerable: true,
get: function get() {
return _io.cancel;
}
});
Object.defineProperty(exports, 'as', {
enumerable: true,
get: function get() {
return _io.as;
}
});
var _utils = require('./utils');
var _runSaga = require('./runSaga');
var _proc = require('./proc');
var _proc2 = _interopRequireDefault(_proc);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = function () {
for (var _len = arguments.length, sagas = Array(_len), _key = 0; _key < _len; _key++) {
sagas[_key] = arguments[_key];
Object.defineProperty(exports, 'runSaga', {
enumerable: true,
get: function get() {
return _runSaga.runSaga;
}
});
Object.defineProperty(exports, 'storeIO', {
enumerable: true,
get: function get() {
return _runSaga.storeIO;
}
});
return function (_ref) {
var getState = _ref.getState;
var dispatch = _ref.dispatch;
var _middleware = require('./middleware');
var cbs = [];
var _middleware2 = _interopRequireDefault(_middleware);
sagas.forEach(function (saga) {
(0, _proc2.default)(saga(getState), subscribe, dispatch, saga.name);
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
return function (next) {
return function (action) {
var result = next(action); // hit reducers
cbs.forEach(function (cb) {
return cb(action);
});
return result;
};
};
function subscribe(cb) {
cbs.push(cb);
return function () {
return (0, _utils.remove)(cbs, cb);
};
}
};
};
exports.default = _middleware2.default;

@@ -6,3 +6,3 @@ "use strict";

});
exports.as = exports.JOIN_ARG_ERROR = exports.FORK_ARG_ERROR = exports.CPS_FUNCTION_ARG_ERROR = exports.CALL_FUNCTION_ARG_ERROR = undefined;
exports.as = exports.CANCEL_ARG_ERROR = exports.JOIN_ARG_ERROR = exports.FORK_ARG_ERROR = exports.CPS_FUNCTION_ARG_ERROR = exports.CALL_FUNCTION_ARG_ERROR = undefined;
exports.matcher = matcher;

@@ -13,5 +13,7 @@ exports.take = take;

exports.call = call;
exports.apply = apply;
exports.cps = cps;
exports.fork = fork;
exports.join = join;
exports.cancel = cancel;

@@ -22,9 +24,9 @@ var _utils = require("./utils");

var CALL_FUNCTION_ARG_ERROR = exports.CALL_FUNCTION_ARG_ERROR = "io.call first argument must be a function";
var CPS_FUNCTION_ARG_ERROR = exports.CPS_FUNCTION_ARG_ERROR = "io.cps first argument must be a function";
var FORK_ARG_ERROR = exports.FORK_ARG_ERROR = "io.fork first argument must be a generator function or an iterator";
var JOIN_ARG_ERROR = exports.JOIN_ARG_ERROR = "io.join argument must be a valid task (a result of io.fork)";
var CALL_FUNCTION_ARG_ERROR = exports.CALL_FUNCTION_ARG_ERROR = "call first argument must be a function";
var CPS_FUNCTION_ARG_ERROR = exports.CPS_FUNCTION_ARG_ERROR = "cps first argument must be a function";
var FORK_ARG_ERROR = exports.FORK_ARG_ERROR = "fork first argument must be a generator function or an iterator";
var JOIN_ARG_ERROR = exports.JOIN_ARG_ERROR = "join argument must be a valid task (a result of a fork)";
var CANCEL_ARG_ERROR = exports.CANCEL_ARG_ERROR = "cancel argument must be a valid task (a result of a fork)";
var IO = Symbol('IO');
var TAKE = 'TAKE';

@@ -37,2 +39,3 @@ var PUT = 'PUT';

var JOIN = 'JOIN';
var CANCEL = 'CANCEL';

@@ -89,4 +92,10 @@ var effect = function effect(type, payload) {

return apply(null, fn, args);
}
function apply(context, fn) {
var args = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
(0, _utils.check)(fn, _utils.is.func, CALL_FUNCTION_ARG_ERROR);
return effect(CALL, { fn: fn, args: args });
return effect(CALL, { context: context, fn: fn, args: args });
}

@@ -111,4 +120,8 @@

var isForkedTask = function isForkedTask(task) {
return task[_utils.TASK];
};
function join(taskDesc) {
if (!taskDesc[_utils.TASK]) throw new Error(JOIN_ARG_ERROR);
if (!isForkedTask(taskDesc)) throw new Error(JOIN_ARG_ERROR);

@@ -118,2 +131,8 @@ return effect(JOIN, taskDesc);

function cancel(taskDesc) {
if (!isForkedTask(taskDesc)) throw new Error(CANCEL_ARG_ERROR);
return effect(CANCEL, taskDesc);
}
var as = exports.as = {

@@ -140,3 +159,6 @@ take: function take(effect) {

return effect && effect[IO] && effect[JOIN];
},
cancel: function cancel(effect) {
return effect && effect[IO] && effect[CANCEL];
}
};

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

});
exports.NOT_ITERATOR_ERROR = undefined;
exports.SagaCancellationException = exports.CANCEL = exports.MANUAL_CANCEL = exports.RACE_AUTO_CANCEL = exports.PARALLEL_AUTO_CANCEL = exports.NOT_ITERATOR_ERROR = undefined;
exports.default = proc;

@@ -14,2 +14,8 @@

var _monitorActions = require('./monitorActions');
var monitorActions = _interopRequireWildcard(_monitorActions);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

@@ -19,69 +25,133 @@

var NOT_ITERATOR_ERROR = exports.NOT_ITERATOR_ERROR = "proc first argument (Saga function result) must be an iterator";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var NOT_ITERATOR_ERROR = exports.NOT_ITERATOR_ERROR = 'proc first argument (Saga function result) must be an iterator';
var PARALLEL_AUTO_CANCEL = exports.PARALLEL_AUTO_CANCEL = 'PARALLEL_AUTO_CANCEL';
var RACE_AUTO_CANCEL = exports.RACE_AUTO_CANCEL = 'RACE_AUTO_CANCEL';
var MANUAL_CANCEL = exports.MANUAL_CANCEL = 'MANUAL_CANCEL';
var nextEffectId = (0, _utils.autoInc)();
var CANCEL = exports.CANCEL = Symbol('@@redux-saga/cancelPromise');
var SagaCancellationException = exports.SagaCancellationException = function SagaCancellationException(type, saga, origin) {
_classCallCheck(this, SagaCancellationException);
this.type = type;
this.saga = saga;
this.origin = origin;
};
function proc(iterator) {
var subscribe = arguments.length <= 1 || arguments[1] === undefined ? function () {
return function () {};
return _utils.noop;
} : arguments[1];
var dispatch = arguments.length <= 2 || arguments[2] === undefined ? function () {} : arguments[2];
var dispatch = arguments.length <= 2 || arguments[2] === undefined ? _utils.noop : arguments[2];
var monitor = arguments.length <= 3 || arguments[3] === undefined ? _utils.noop : arguments[3];
var parentEffectId = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
var name = arguments.length <= 5 || arguments[5] === undefined ? 'anonymous' : arguments[5];
(0, _utils.check)(iterator, _utils.is.iterator, NOT_ITERATOR_ERROR);
var deferredInput = undefined,
deferredEnd = undefined;
var deferredInputs = [];
var canThrow = _utils.is.throw(iterator);
var deferredEnd = (0, _utils.deferred)();
var endP = new Promise(function (resolve, reject) {
return deferredEnd = { resolve: resolve, reject: reject };
});
var unsubscribe = subscribe(function (input) {
if (deferredInput && deferredInput.match(input)) deferredInput.resolve(input);
deferredInputs.forEach(function (def) {
if (def.match(input)) def.resolve(input);
});
});
iterator._isRunning = true;
next();
iterator._isRunning = true;
return endP;
return newTask(parentEffectId, name, iterator, deferredEnd.promise);
function next(arg, isError) {
//console.log('next', arg, isError)
deferredInput = null;
if (!iterator._isRunning) return;
try {
if (isError && !canThrow) throw arg;
var result = isError ? iterator.throw(arg) : iterator.next(arg);
if (!result.done) {
//console.log('yield', name, result.value)
runEffect(result.value).then(next, function (err) {
var currentEffect = runEffect(result.value, parentEffectId);
deferredEnd.promise[CANCEL] = currentEffect[CANCEL];
currentEffect.then(next, function (err) {
return next(err, true);
});
} else {
//console.log('return', name, result.value)
iterator._isRunning = false;
iterator._result = result.value;
unsubscribe();
deferredEnd.resolve(result.value);
end(result.value);
}
} catch (err) {
//console.log('catch', name, err)
iterator._isRunning = false;
iterator._error = err;
unsubscribe();
deferredEnd.reject(err);
} catch (error) {
/*eslint-disable no-console*/
console.warn(name + ': uncaught', error);
end(error, true);
}
}
function runEffect(effect) {
function end(result, isError) {
iterator._isRunning = false;
if (!isError) {
iterator._result = result;
deferredEnd.resolve(result);
} else {
iterator._error = result;
deferredEnd.reject(result);
}
unsubscribe();
}
function runEffect(effect, parentEffectId) {
var label = arguments.length <= 2 || arguments[2] === undefined ? '' : arguments[2];
var effectId = nextEffectId();
monitor(monitorActions.effectTriggered(effectId, parentEffectId, label, effect));
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)) ? 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);
var promise = _utils.is.array(effect) ? runParallelEffect(effect, effectId) : _utils.is.iterator(effect) ? proc(effect, subscribe, dispatch, monitor, effectId).done : (data = _io.as.take(effect)) ? runTakeEffect(data) : (data = _io.as.put(effect)) ? runPutEffect(data) : (data = _io.as.race(effect)) ? runRaceEffect(data, effectId) : (data = _io.as.call(effect)) ? runCallEffect(data.context, data.fn, data.args, effectId) : (data = _io.as.cps(effect)) ? runCPSEffect(data.fn, data.args) : (data = _io.as.fork(effect)) ? runForkEffect(data.task, data.args, effectId) : (data = _io.as.join(effect)) ? runJoinEffect(data) : (data = _io.as.cancel(effect)) ? runCancelEffect(data) : /* resolve anything else */Promise.resolve(effect);
var def = (0, _utils.deferred)();
var isRunning = true;
var completeWith = function completeWith(fn) {
return function (outcome) {
if (isRunning) {
isRunning = false;
fn(outcome);
}
};
};
promise.then(completeWith(def.resolve), completeWith(def.reject));
def.promise[CANCEL] = function (_ref) {
var type = _ref.type;
var origin = _ref.origin;
if (isRunning) {
isRunning = false;
var error = new SagaCancellationException(type, name, origin);
cancelPromise(promise, error);
def.reject(error);
}
};
def.promise.then(function (result) {
return monitor(monitorActions.effectResolved(effectId, result));
}, function (error) {
return monitor(monitorActions.effectRejected(effectId, error));
});
return def.promise;
}
function runTakeEffect(pattern) {
return new Promise(function (resolve) {
deferredInput = { resolve: resolve, match: (0, _io.matcher)(pattern), pattern: pattern };
});
var def = (0, _utils.deferred)({ match: (0, _io.matcher)(pattern), pattern: pattern });
deferredInputs.push(def);
var done = function done() {
return (0, _utils.remove)(deferredInputs, def);
};
def.promise.then(done, done);
def.promise[CANCEL] = done;
return def.promise;
}
function runPutEffect(action) {
return Promise.resolve(1).then(function () {
return (0, _utils.asap)(function () {
return dispatch(action);

@@ -91,5 +161,5 @@ });

function runCallEffect(fn, args) {
var result = fn.apply(undefined, _toConsumableArray(args));
return !_utils.is.iterator(result) ? Promise.resolve(result) : proc(result, subscribe, dispatch);
function runCallEffect(context, fn, args, effectId) {
var result = fn.apply(context, args);
return !_utils.is.iterator(result) ? Promise.resolve(result) : proc(result, subscribe, dispatch, monitor, effectId, fn.name).done;
}

@@ -105,7 +175,4 @@

function runForkEffect(task, args) {
var _taskDesc;
function runForkEffect(task, args, effectId) {
var result = undefined,
_generator = undefined,
_iterator = undefined;

@@ -120,3 +187,2 @@ var isFunc = _utils.is.func(task);

if (_utils.is.iterator(result)) {
_generator = task;
_iterator = result;

@@ -150,21 +216,38 @@ }

var _done = proc(_iterator, subscribe, dispatch);
var taskDesc = (_taskDesc = {}, _defineProperty(_taskDesc, _utils.TASK, true), _defineProperty(_taskDesc, '_generator', _generator), _defineProperty(_taskDesc, '_iterator', _iterator), _defineProperty(_taskDesc, '_done', _done), _defineProperty(_taskDesc, 'name', isFunc ? task.name : 'anonymous'), _defineProperty(_taskDesc, 'isRunning', function isRunning() {
return _iterator._isRunning;
}), _defineProperty(_taskDesc, 'result', function result() {
return _iterator._result;
}), _defineProperty(_taskDesc, 'error', function error() {
return _iterator._error;
}), _taskDesc);
return Promise.resolve(taskDesc);
var name = isFunc ? task.name : 'anonymous';
return Promise.resolve(proc(_iterator, subscribe, dispatch, monitor, effectId, name, true));
}
function runJoinEffect(task) {
return task._done;
return task.done;
}
function runRaceEffect(effects) {
return Promise.race(Object.keys(effects).map(function (key) {
return runEffect(effects[key]).then(function (result) {
function runCancelEffect(task) {
task.done[CANCEL](new SagaCancellationException(MANUAL_CANCEL, '', name));
return Promise.resolve();
}
function runParallelEffect(effects, effectId) {
var promises = effects.map(function (eff) {
return runEffect(eff, effectId);
});
var ret = Promise.all(promises);
ret[CANCEL] = function (error) {
promises.forEach(function (p) {
return cancelPromise(p, error);
});
};
ret.catch(function () {
ret[CANCEL](new SagaCancellationException(PARALLEL_AUTO_CANCEL, name, name));
});
return ret;
}
function runRaceEffect(effects, effectId) {
var promises = [];
var retP = Promise.race(Object.keys(effects).map(function (key) {
var promise = runEffect(effects[key], effectId, key);
promises.push(promise);
return promise.then(function (result) {
return _defineProperty({}, key, result);

@@ -175,3 +258,31 @@ }, function (error) {

}));
retP[CANCEL] = function (error) {
promises.forEach(function (p) {
return cancelPromise(p, error);
});
};
var done = function done() {
return retP[CANCEL](new SagaCancellationException(RACE_AUTO_CANCEL, name, name));
};
retP.then(done, done);
return retP;
}
function newTask(id, name, iterator, done, forked) {
var _ref3;
return _ref3 = {}, _defineProperty(_ref3, _utils.TASK, true), _defineProperty(_ref3, 'id', id), _defineProperty(_ref3, 'name', name), _defineProperty(_ref3, 'done', done), _defineProperty(_ref3, 'forked', forked), _defineProperty(_ref3, 'isRunning', function isRunning() {
return iterator._isRunning;
}), _defineProperty(_ref3, 'getResult', function getResult() {
return iterator._result;
}), _defineProperty(_ref3, 'getError', function getError() {
return iterator._error;
}), _ref3;
}
function cancelPromise(promise, error) {
if (promise[CANCEL]) promise[CANCEL](error);
}
}
'use strict';
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
Object.defineProperty(exports, "__esModule", {

@@ -8,2 +10,6 @@ value: true

exports.remove = remove;
exports.deferred = deferred;
exports.arrayOfDeffered = arrayOfDeffered;
exports.autoInc = autoInc;
exports.asap = asap;

@@ -52,2 +58,5 @@ var _marked = [sampleGen].map(regeneratorRuntime.mark);

return it && typeof it.throw === 'function';
},
task: function task(it) {
return it && it[TASK];
}

@@ -59,2 +68,36 @@ };

if (index >= 0) array.splice(index, 1);
}
function deferred() {
var props = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var def = _extends({}, props);
var promise = new Promise(function (resolve, reject) {
def.resolve = resolve;
def.reject = reject;
});
def.promise = promise;
return def;
}
function arrayOfDeffered(length) {
var arr = [];
for (var i = 0; i < length; i++) {
arr.push(deferred());
}
return arr;
}
function autoInc() {
var seed = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
return function () {
return ++seed;
};
}
function asap(action) {
return Promise.resolve(1).then(function () {
return action();
});
}
{
"name": "redux-saga",
"version": "0.3.3",
"version": "0.4.0",
"description": "Saga middleware for Redux to handle Side Effects",

@@ -11,18 +11,16 @@ "main": "lib/index.js",

"compile": "rimraf lib && babel -d lib/ src/",
"prepublish": "npm run check && npm run compile",
"build:umd:dev": "webpack src/index.js dist/redux-saga.js --config webpack.config.dev.js",
"build:umd:prod": "webpack src/index.js dist/redux-saga.min.js --config webpack.config.prod.js",
"build:umd": "rimraf dist && npm run build:umd:dev && npm run build:umd:prod",
"prepublish": "npm run check && npm run compile && npm run build:umd",
"counter": "budo examples/counter/src/main.js:build.js --dir examples/counter --verbose --live -- -t babelify",
"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",
"shop": "budo examples/shopping-cart/src/main.js:build.js --dir examples/shopping-cart --verbose --live -- -t babelify",
"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",
"async": "budo examples/async/src/main.js:build.js --dir examples/async --verbose --live -- -t babelify",
"build-async": "browserify --debug examples/async/src/main.js -t babelify --outfile examples/async/build.js",
"build-examples": "npm run build-counter && npm run build-shop && npm run build-async",
"test-examples": "npm run test-counter && npm run test-shop",
"real-world": "node examples/real-world/server.js"

@@ -51,4 +49,6 @@ },

"babel-cli": "^6.1.18",
"babel-core": "6.4.0",
"babel-eslint": "^4.1.5",
"babel-polyfill": "^6.2.0",
"babel-loader": "6.2.1",
"babel-polyfill": "6.3.14",
"babel-preset-es2015": "^6.1.18",

@@ -67,4 +67,14 @@ "babel-preset-react": "^6.1.18",

"tap-spec": "^4.1.1",
"tape": "^4.2.2"
}
"tape": "^4.2.2",
"webpack": "1.12.10"
},
"npmName": "redux-saga",
"npmFileMap": [
{
"basePath": "/dist/",
"files": [
"*.js"
]
}
]
}

@@ -0,1 +1,5 @@

# redux-saga
[![npm version](https://img.shields.io/npm/v/redux-saga.svg?style=flat-square)](https://www.npmjs.com/package/redux-saga)
An alternative Side Effect model for Redux applications. Instead of dispatching thunks

@@ -41,3 +45,5 @@ which get handled by the redux-thunk middleware. You create *Sagas* to gather all your

- [Non blocking calls with fork/join](#non-blocking-calls-with-forkjoin)
- [Task cancellation](#task-cancellation)
- [Building examples from sources](#building-examples-from-sources)
- [Using umd build in the browser](#using-umd-build-in-the-browser)

@@ -207,3 +213,3 @@ #Getting started

const iterator = fetchSaga()
assert.deepEqual(iterator.next().value, call(fetch, '/products') // expects a call(...) value
assert.deepEqual(iterator.next().value, call(fetch, '/products')) // expects a call(...) value
```

@@ -501,18 +507,159 @@

// non blocking call
const task = yield fork(subtask, ...args)
function* child() { ... }
// ... later
// now a blocking call, will resume with the outcome of task
const result = yield join(task)
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)
}
```
You can also ask a Task if it's still running
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>
#Task cancellation
Once a task is forked, you can abort its execution using `yield cancel(task)`. Cancelling
a running task will throw a `SagaCancellationException` inside it.
To see how it works, let's consider a simple example. A background sync which can be
started/stopped by some UI commands. Upon receiving a `START_BACKGROUND_SYNC` action,
we fork a background task that will periodically sync some data from a remote server.
The task will execute continually until a `STOP_BACKGROUND_SYNC` action is triggered.
Then we cancel the background task and wait again for the next `START_BACKGROUND_SYNC` action.
```javascript
// attention, we don't use yield
const stillRunning = task.isRunning()
import { take, put, call, fork, cancel, SagaCancellationException } from 'redux-saga'
import actions from 'somewhere'
import { someApi, delay } from 'somewhere'
function* bgSync() {
try {
while(true) {
yield put(actions.requestStart())
const result = yield call(someApi)
yield put(actions.requestSuccess(result))
yield call(delay, 5000)
}
} catch(error) {
if(error instanceof SagaCancellationException)
yield put(actions.requestFailure('Sync cancelled!'))
}
}
function* main() {
while( yield take(START_BACKGROUND_SYNC) ) {
// starts the task in the background
const bgSyncTask = yield fork(bgSync)
// wait for the user stop action
yield take(STOP_BACKGROUND_SYNC)
// user clicked stop. cancel the background task
// this will throw a SagaCancellationException into task
yield cancel(bgSyncTask)
}
}
```
`yield cancel(bgSyncTask)` will throw a `SagaCancellationException`
inside the currently running task. In the above example, the exception is caught by
`bgSync`. Otherwise, it will propagate up to `main`. And it if `main` doesn't handle it
then it will bubble up the call chain, just as normal JavaScript errors bubble up the
call chain of synchronous functions.
Cancelling a running task will also cancel the current effect where the task is blocked
at the moment of cancellation.
For example, suppose that at a certain point in application lifetime, we had this pending call chain
```javascript
function* main() {
const task = yield fork(subtask)
...
// later
yield cancel(task)
}
function* subtask() {
...
yield call(subtask2) // currently blocked on this call
...
}
function* subtask2() {
...
yield call(someApi) // currently blocked on this all
...
}
```
`yield cancel(task)` will trigger a cancellation on `subtask`, which in turn will trigger
a cancellation on `subtask2`. A `SagaCancellationException` will be thrown inside `subtask2`,
then another `SagaCancellationException` will be thrown inside `subtask`. If `subtask`
omits to handle the cancellation exception, it will propagate up to `main`.
The main purpose of the cancellation exception is to allow cancelled tasks to perform any
cleanup logic. So we wont leave the application in an inconsistent state. In the above example
of background sync, by catching the cancellation exception, `bgSync` is able to dispatch a
`requestFailure` action to the store. Otherwise, the store could be left in a inconsistent
state (e.g. waiting for the result of a pending request)
>It's important to remember that `yield cancel(task)` doesn't wait for the cancelled task
to finish (i.e. to perform its catch block). The cancel effect behave like fork. It returns
as soon as the cancel was initiated.
>Once cancelled, a task should normally return as soon as it finishes its cleanup logic.
In some cases, the cleanup logic could involve some async operations, but the cancelled
task lives now as a separate process, and there is no way for it to rejoin the main
control flow (except dispatching actions other tasks via the Redux store. However
this will lead to complicated control flows that ae hard to reason about. It's always preferable
to terminate a cancelled task asap).
##Automatic cancellation
Besides manual cancellation. There are cases where cancellation is triggered automatically
1- In a `race` effect. All race competitors, except the winner, are automatically cancelled.
2- In a parallel effect (`yield [...]`). The parallel effect is rejected as soon as one of the
sub-effects is rejected (as implied by Promise.all). In this case, all the other sub-effects
are automatically cancelled.
Unlike in manual cancellations, unhandled cancellation exceptions are not propagated to the actual
saga running the race/parallel effect. Nevertheless, a warning is logged into the console in case
a cancelled task omitted to handle a cancellation exception.
#Building examples from sources

@@ -578,1 +725,11 @@

```
#Using umd build in the browser
There's an **umd** build of `redux-saga` available in `dist/` folder. Using the umd build `redux-saga` is available as `ReduxSaga` in the window object.
The umd version is useful if you don't use webpack or browserify, you can access it directly from [npmcdn](npmcdn.com).
The following builds are available:
[https://npmcdn.com/redux-saga/dist/redux-saga.js](https://npmcdn.com/redux-saga/dist/redux-saga.js)
[https://npmcdn.com/redux-saga/dist/redux-saga.min.js](https://npmcdn.com/redux-saga/dist/redux-saga.min.js)
**Important!** If the browser you are targeting doesn't support _es2015 generators_ you must provide a valid polyfill, for example the one provided by *babel*: [browser-polyfill.min.js](https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser-polyfill.min.js). The polyfill must be imported before **redux-saga**.

Sorry, the diff of this file is not supported 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