Archived Repository The code of this repository was written during a Hack Day by a Marmelab developer. It's part of the distributed R&D effort at Marmelab, where each developer spends 2 days a month for learning and experimentation. This code is not intended to be used in production, and is not maintained. |
Utility to handle side effects with generators. Inspired by redux-saga and co.
An effect is an object that describe a task, but do not execute it. This allows to separate the tasks scheduling from the tasks themself.
npm install
The sg function takes a generator yielding effect and returns a function which return a promise.
import { call } from 'sg/effects';
function* gen(name) {
//...
yield call(console.log, `hello ${name}`);
return 'done';
}
const func = sg(gen);
func('world')
.then((result) => {
console.log(result); // done
});
The effects are helper functions which return an object describing what to do.
const effect = call(console.log, 'hello world');
// effect:
{
type: 'call',
handler: // function that will execute the effect: called with arg, and returning a promise
args: [console.log, 'hello world'], // args passed to the effect function
}
Calls a function (normal, async or returning a promise) or a generator yielding effect and either returns the value or throws an error.
const add = (a, b) => a + b;
const addPromise = (a, b) => new Promise((resolve, reject) => {
try {
resolve(a + b);
} catch (error) {
reject(error);
}
})
const addAsync = async (a, b) => await Promise.resolve(a + b);
function* addGenerator(a, b) {
const c = yield call(add, a, b);
const d = yield call(addPromise, a, c);
return yield call(addAsync, b, d);
}
Calls a thunk function and either returns the value or throws an error. A thunk function is a function that returns an asynchronous function taking a callback.
const add = (a, b) => cb => cb(null, a + b);
Calls a co style generator (yielding promise or thunk, not effect).
function* add(a, b) {
return yield Promise.resolve(a + b);
}
Calls a continuation passing style function.
const add = (a, b, cb) => cb(null, a + b);
emit an event
yield put('my event', { payload: data });
Waits for event sent by put and return its payload
const payload = yield take('my event'); // { payload: data }
Launches another sg generator, but do not wait for it to end, returning a task object.
const task = yield spawn(sgGenerator);
Same as spawn, but errors from the forked generator will bubble up to the parent generator making it fail. Also the parent generator will wait for the forked generator to end before resolving.
const task = yield fork(sgGenerator);
takes a task returned by fork or spawn, and cancels it, ending it and its children.
yield cancel(task);
takes a task returned by fork or spawn, and joins it, waiting for the task to end or throw an error.
const result = yield join(task);
yields several effect simultaneously in a literal and returns only the result of the first completed effect, or its error if it failed.
const { user, cancel } = yield race({
user: yield call(fetchUser),
cancel: yield take('CANCEL'),
});
forks given generator each time given event is triggered. It is the same as forking the following generator:
function* takeEverySaga(type, gen, ...args) {
while (true) {
const action = yield take(type);
yield fork(gen, ...args.concat(action));
}
}
Object returned by fork and spawn. It has a done and cancel method.
- done: returns a promise, which resolves with task result or rejects with task error.
- cancel: Cancels the task if it is still running. This in turn will also cancel all children of this task.
You can create your own effect with createEffect. It takes a type, and an handler.
- Type: A string with the effect name
- Handler:
a function returning a promise and that receives three arguments:
- effect parameters: an array with the list of argument passed to the effect function
- emitter An event emitter used internally for the take and put effects.
- id The internal id of the current saga. You will probably never need this.
Example of custom effect:
import sg, { createEffect } from 'sg.js';
import { createEffect } from 'sg.js/effects';
function handleSql([sql, parameter]) {
return executeQueryAndReturnAPromise(sql, parameter);
}
const sqlEffect = createEffect('sql', handleSql);
sqlEffect('query', 'parameter');
give
{
type: 'sql',
handle: handleSql,
args: ['query', 'parameter'],
}
Our sqlEffect can be used in a generator like this.
sg(function* (userData) {
yield sqlEffect('INSERT ... INTO user', userData);
})({ name: 'john' });
During execution handleSql will get called like so
handleSql(['INSERT ... INTO user', userData]);
Sg use an eventEmitter internally to handle take and put effects. It is possible to pass your own eventEmitter to sg. This allows to take events from this event emitter. Your event emitter must extends node event emitter.