Elmish.Throttle
Throttles events so they can occur at max once per time interval.
Usage
Store the ThrottleState
in your model:
open Fable.Elmish.Throttle
type Model = {
Clicks : int
ThrottleState: Map<string, Throttle.Status>
}
And have a way to dispatch a message from the throttler:
type Msg =
| IncrementClick
| ThrottleMsg of Throttle.Msg
On your event, call throttle
. Note how we identify the thing we want to throttle with "button-click"
. If you want multiple things to throttle each other, use the same Id.
let throttleOnClick model ev =
throttle model.ThrottleState "button-click" (TimeSpan.FromSeconds 1.) ev
let button() =
button [ OnClick (fun ev ->
match throttleOnClick model ev with
| None -> () // if the event is throttled, nothing happens
| Some (ev, throttleMsg) ->
// if the event is not throttled, it is returned here
// note that you must dispatch the throttle message and handle it
dispatch (ThrottleMsg throttleMsg)
IncrementClick |> dispatch
)
] [ str "Click me!" ]
Handle the throttle message by calling handleThrottleMsg
. You'll get back a command to release the resource once the timer expires, so it's important to make sure that command is run.
let update msg model : (Model * Cmd<Msg>)=
match msg with
| IncrementClick -> { model with Clicks = model.Clicks + 1}, Cmd.none
| ThrottleMsg throttleMsg ->
// let the throttler handle the message
let throttleResult = handleThrottleMsg throttleMsg model.ThrottleState
match throttleResult with
// get back a new state and a command
| Ok (throttleState, throttleCmd) ->
// map the command so it's run
{ model with ThrottleState = throttleState }, Cmd.map ThrottleMsg throttleCmd
| Error e ->
printfn "Error throttling: %A" e
model, Cmd.none
Run the example to see it in action.
How is this different from debouncing?
Debouncing resets the timer on an action, meaning continuous actions (such as clicking a button repeatedly) won't ever fire an event. This throttler limits actions to once per X interval, allowing some events through even if the events are continuous. Pick the one that suits your needs, neither one fits all scenarios.
Building
Make sure the following requirements are installed in your system:
- dotnet SDK 2.0 or higher
- node.js 6.11 or higher
- yarn
- Mono if you're on Linux or macOS.
Then you just need to type ./build.cmd
or ./build.sh
Release
In order to push your package to [nuget.org][https://nuget.org] you need to add your API keys to NUGET_KEY
environmental variable.
You can create a key here.
- Update CHANGELOG.md with a new version, data and release notes ReleaseNotesHelper. Ex:
#### 0.2.0 - 30.04.2017
* FEATURE: Does cool stuff!
* BUGFIX: Fixes that silly oversight
- You can then use the Release target. This will:
- make a commit bumping the version: Bump version to 0.2.0
- publish the package to nuget
- push a git tag
./build.sh Release
MacOS/Linux | Windows |
---|---|
Nuget
Stable | Prerelease |
---|---|
A note about Fable Libraries vs Fable Bindings
There are two kinds of Fable packages:
- JS Bindings: These don't contain any actual code, only signatures and attributes normally generated by ts2fable. They're just used to provide a type-safe to interact with a JS library from Fable.
- Libraries: These contain F# code that will be compiled to JS by Fable when being referenced by a consumer project.
The distinction is important because Fable can read metadata from .dll assemblies (like signatures and attributes) but not executable code, for that it needs the F# sources. For JS bindings without actual code you don't need to worry as they can be distributed as any other Nuget package. However, Fable libraries need a couple of extra steps:
- First, the package must contain a folder named fable with the F# sources (and if necessary other files like JS scripts).
- The
fable
folder must contain an .fsproj file with the same name as the Nuget package.
This is not difficult to do and usually only requires adding a tag to your project file (example), but you need to make sure all the sources get into the package with the proper directory structure. Also, take into account Fable will just make a simple XML parsing to extract the source files from the .fsproj, so you should avoid MSBuild conditionals, etc.
Because Fable will compile your sources you must be careful with compiler directives too (like #if MY_SYMBOL
, etc). Though you can use this to your advantage and do some logging in debug mode (#if DEBUG
). And remember also that Fable will always define the FABLE_COMPILER
symbol when compiling to JS.