Core Effects

The basic effects that Ferp provides to help you build better software

effects.none()

The none effect is very simple, there is no effect. Use this effect to tell your app there is nothing to do after an update. All arguments passed to it are ignored, and your update function will not be called.

effects.defer(promise)

A deferred promise is a great way to connect things like timers, http requests, and other asynchronous code to your update loop. There is a hitch, though - deferred effects should never throw an error. This means that if your effect has the chance of causing a run-time error, it must be caught inside your effects.defer call, and an alternative effect should be returned. Here is an example of a bad deferred effect:

badDeferredEffect.js
import { effects } from 'ferp';
const fetchEffect = (url, options, okEffect) => effects.defer(
fetch(url, options)
.then(response => response.json())
.then(okEffect)
);
// Usage:
fetchEffect(
'https://jsonplaceholder.typicode.com/posts',
undefined,
(json) => ({ type: 'httpSuccess', json }),
);

What happens if this fetch fails? It will throw an exception, and crash your app. To counter-act this, provide an explicit error effect:

goodDeferredEffect.js
import { effects } from 'ferp';
const fetchEffect = (url, options, okEffect, errorEffect = effects.none) => effects.defer(
fetch(url, options)
.then(response => response.json())
.then(okEffect)
.catch(errorEffect)
);
// Usage:
fetchEffect(
'https://jsonplaceholder.typicode.com/posts',
undefined,
json => ({ type: 'httpSuccess', json }),
error => ({ type: 'httpFailure', error }),
);

This provides your app with a reasonable way to manage the error without being opinionated.

effects.thunk(() => effect)

Thunks are a way to prevent an effect from running until the effects manager processes the effect. The wait can be from various factors, including a deferred promise or a desired synchronous batch behaviour. A great example use case for a thunk is chaining a series of timeouts. Consider the following example:

badDelayEffect.js
import { effects } from 'ferp';
const delay = (milliseconds, effect) => effects.defer(new Promise((resolve) => {
setTimeout(() => resolve(effect()), milliseconds);
}));
delay(1000, () => effects.batch([
'first timer done',
delay(1000, () => 'second timer done'),
]));

You might expect something like /* wait 1 second */, 'first timer done', /* wait 1 second */, 'second timer done', but in reality, it will be /* wait 1 second */, 'first timer done', 'second timer done'. This is because the promises are kicked off immediately, before the effect manager handles them. In some cases, like non-dependent http requests, this is great, but for chaining timers, it is not. The solution, as you might have guessed, is wrapping our delay effect in a thunk, just like this:

goodDelayEffect.js
import { effects } from 'ferp';
const delay = (milliseconds, effect) => effects.thunk(() => effects.defer(new Promise((resolve) => {
setTimeout(() => resolve(effect()), milliseconds);
})));
delay(1000, () => effects.batch([
'first timer done',
delay(1000, () => 'second timer done'),
]));

Now each delay waits until it is called, with a 1 second timeout before each message. Thunks are a great tool for effect flow control.

effects.batch(arrayOfEffects)

Batch is the other powerful flow control tool for effects. Often times, an update will only need to do a single effect, but this is not the only case. Batching effects gives you the opportunity to run multiple side effects at once. This is especially powerful when you need to pass a message to your update, while also kicking off a background task.

<a message>

While you can use any number of the above effects, any other value is treated as a message that can be passed into your update function. For instance, the redux-style patter may have a message that looks like:

{
"type": "some message type here",
"foo": true,
"bar": { "baz": "fizz" },
}

You may find you need simpler messages for some apps that don't require an object structure, or maybe you find yourself making complicated class structures. Ferp doesn't care, as long as you can make them unique.