Skip to content
This repository has been archived by the owner on Apr 17, 2019. It is now read-only.
/ sg Public archive

Side Effect Handling library - Think Redux-Saga without Redux

License

Notifications You must be signed in to change notification settings

marmelab/sg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hackday 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.

SG

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.

install

npm install

Introduction

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
});

effects

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
}

call

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);
}

thunk

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);

co

Calls a co style generator (yielding promise or thunk, not effect).

function* add(a, b) {
    return yield Promise.resolve(a + b);
}

cps

Calls a continuation passing style function.

const add = (a, b, cb) => cb(null, a + b);

put

emit an event

yield put('my event', { payload: data });

take

Waits for event sent by put and return its payload

const payload = yield take('my event'); // { payload: data }

spawn

Launches another sg generator, but do not wait for it to end, returning a task object.

const task = yield spawn(sgGenerator);

fork

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);

cancel

takes a task returned by fork or spawn, and cancels it, ending it and its children.

yield cancel(task);

join

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);

race

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'),
});

takeEvery

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));
    }
}

task

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.

Adding your own custom effects with createEffect

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]);

Injecting Custom EventEmitter

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.

About

Side Effect Handling library - Think Redux-Saga without Redux

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published