FCF is a Functional Control Flow Micro-Library for JavaScript written in
TypeScript. It aims to provide a functional and semantic alternative to native
JavaScript control flow statements such as if
, switch
and while
.
Keywords like if
or switch
are imperative statements whose syntax can often
complicate or obscure an expression's semantics, e.g.:
// BAD imperative (non semantic)
if (document.visibilityState === 'visible') {
// do stuff with the visible document
} else if (document.visibilityState === 'hidden') {
// do stuff with the hidden document
}
// a bit better... with semantic functions
const isDocumentVisible = () => document.visibilityState === 'visible'
const isDocumentHidden = () => document.visibilityState === 'hidden'
// notice that we don't store the value of the conditions here.
// for that we need to introduce new variables
if (isDocumentVisible()) {
// do stuff with the visible document
} else if (isDocumentHidden()) {
// do stuff with the hidden document
}
With FCF, we can structure the program flow in a more semantic way, while retaining the logic of its conditional statements:
import fcf from 'fcf'
const checkIfApplicationIsActive = fcf
.if(value => value === 'visible')
.then(() => {
// do stuff with the visible document
return 'active'
})
.elseIf(value => value === 'hidden')
.then(() => {
// do stuff with the hidden document
return 'standby'
})
// check the condition
const {value} = checkIfApplicationIsActive.run(document.visibilityState)
// the value returned by the first matched `then` call ('active'|'standby')
console.log(value)
Notice that FCF is strictly typed so we can rewrite the example above in TypeScript in this way:
import fcf from 'fcf'
const checkIfApplicationIsActive = fcf
// with `[string]` we can define the type of arguments provided to the 'run' function.
// `string` is the value retained by the fcf.if object
.if<[string], string>(value => value === 'visible')
.then(() => {
// do stuff with the visible document
return 'active'
})
// `value` here is of type `string`
.elseIf(value => value === 'hidden')
.then(() => {
// do stuff with the hidden document
return 'standby'
})
// check the condition
const {value} = checkIfApplicationIsActive.run(document.visibilityState)
// will be of type "string"
console.log(value)
Another big advantage of FCF is that its API is composable:
const greetUserByRole = fcf
.switch(user => user.role)
.case('power')
.then(() => {
console.log('good morning, power user')
})
.case('base')
.then(() => {
console.log('hello, base user')
})
.default(() => {
console.log('hello, whoever you are')
})
const checkUser = fcf
.if(user => user.isLoggedIn)
.then(user => greetUserByRole.run(user))
.else(() => {
console.log('you are not logged in')
})
checkUser.run({
role: 'power',
isLoggedIn: true
})
checkUser.run({
role: 'base',
isLoggedIn: true
})
checkUser.run({
role: 'power',
isLoggedIn: false
})
Check a live demo of the example above
fcf.if
provides an alternative to the native JavaScript if
statement.
fcf
.if(true)
.then(() => {
console.log('hello')
})
.run()
An fcf.if
object has the following properties
else(fn: function)
- provide a fallback method if none of the conditions are matchedelseIf(fn: function|any)
- add a new condition that must be followed by athen
callthen(fn: function)
- add a callback to a previous condition and set thevalue
propertyvalue
- value returned by the first matchingthen
callrun(...args: any[])
- execute the flow, passing any supplied arguments into it
Examples
The simplest fcf.if
might look like this:
fcf
.if(true)
.then(() => {
console.log('hello')
})
.run()
The else
method works the same as it does for if
statements
fcf
.if(false)
.then(() => {
console.log('you will never get here')
})
.else(() => {
console.log('hello')
})
.run()
With the elseIf
method, we can add new conditions
fcf
.if(true)
.then(() => {
console.log('you will never get here')
})
.elseIf(false)
.then(() => {
console.log('hello')
})
.run()
The fcf.if
and fcf.if.elseIf
methods also accept functions as arguments.
fcf
.if(() => true)
.then(() => {
console.log('you will never get here')
})
.elseIf(() => false)
.then(() => {
console.log('hello')
})
.run()
The fcf.if.run
method allows you to pass arguments into your ifFlow chain
fcf
.if(greeting => greeting === 'goodbye')
.then(() => {
console.log('goodbye')
})
.elseIf(greeting => greeting === 'hello')
.then(() => {
console.log('hello')
})
.run('hello')
The fcf.if
object retains the value returned by the first matched then
call
const {value} = fcf
.if(greeting => greeting === 'goodbye')
.then(() => {
return 'goodbye'
})
.elseIf(greeting => greeting === 'hello')
.then(() => {
return 'hello'
})
.run('hello')
console.log(value) // hello
fcf.switch
provides an alternative to the native JavaScript switch
statement. It also normalizes the default switch
behavior, avoiding the need
for break
statements: the first matched case
pre-empts evaluation of
the other cases
fcf
.switch(greeting => greeting)
.case('hello')
.then(() => {
console.log('hello')
})
.case('goodbye')
.then(() => {
console.log('goodbye')
})
.default(() => {
console.log('¯\\_(ツ)_/¯ ')
})
.run('goodbye')
An fcf.switch
object has the following properties
default(fn: function)
- provide a fallback method if nocase
is matchedcase(fn: function|any)
- add a new case that must be followed by athen
callthen(fn: function)
- add a callback to a previouscase
call and optionally set thevalue
propertyvalue
- value returned by the first matchingthen
callrun(...args: any[])
- execute the flow, passing any supplied arguments into it
Examples
The simplest fcf.switch
might look like this:
fcf
.switch(greeting => greeting)
.case('hello')
.then(() => {
console.log('hello')
})
.case('goodbye')
.then(() => {
console.log('goodbye')
})
.default(() => {
console.log('¯\\_(ツ)_/¯ ')
})
.run('goodbye')
The default
method works the same as in regular switch
statements: if no case
is
matched, the default
method is called.
fcf
.switch(greeting => greeting)
.case('goodbye')
.then(() => {
console.log('you will never get here')
})
.default(greeting => {
console.log(greeting)
})
.run('hello')
The fcf.switch
and fcf.switch.case
methods also accept functions as arguments.
fcf
.switch(greeting => greeting)
.case(() => 'goodbye')
.then(() => {
console.log('goodbye')
})
.case(() => 'hello')
.then(() => {
console.log('hello')
})
.run('hello')
The fcf.switch.run
method allows you to pass arguments into your switchFlow chain
fcf
.switch(greeting => greeting)
.case(greeting => greeting === 'goodbye')
.then(() => {
console.log('goodbye')
})
.case(greeting => greeting === 'hello')
.then(() => {
console.log('hello')
})
.run('hello')
The fcf.switch
objects retain the value returned by the first matched then
call
const {value} = fcf
.switch(greeting => greeting)
.case(greeting => greeting === 'goodbye')
.then(() => {
return 'goodbye'
})
.case(greeting => greeting === 'hello')
.then(() => {
return 'hello'
})
.run('hello')
console.log(value) // hello
fcf.while
provides an alternative to the native JavaScript while
statement.
It normalizes its behavior in browsers and Node.js by using
requestAnimationFrame
or setImmediate
to run loops
fcf
.while(true)
.do(() => {
console.log('hello')
})
.run()
An fcf.while
object has the following properties
do(fn: function)
- add a callback that will be called forever when the whileFlow is running. If ado
function returnsfalse
, the while flow is stoppedbreak(fn?: function)
- if called, it stops the while flow. It can take a callback to be called when the flow is stoppedvalue
- value returned by the initial control functionrun(...args: any[])
- execute the flow, passing any supplied arguments into it
Examples
The simplest fcf.while
might look like this:
fcf
.while(true)
.do(() => {
console.log('hello')
})
.run()
The break
allows the while flow to be stopped
const loggerFlow = fcf
.while(true)
.do(() => {
console.log('hello')
})
.run()
// stop the while loop after 1 second
setTimeout(() => {
loggerFlow.break()
}, 1000)
The fcf.while
method can also take a function as an argument.
fcf
// log until document.visibilityState === 'visible'.
// otherwise stop
.while(() => document.visibilityState === 'visible')
.do(() => {
console.log('hello')
})
.run()
The fcf.while.do
can stop the while flow returning false
fcf
.while(true)
// it logs only once and then stops the while flow
.do(() => {
console.log('hello')
return false
})
.run()
The fcf.while.run
method allows you to pass arguments into your whileFlow
chain
const greetUser = fcf
.switch(user => user.role)
.case('power')
.then(() => {
console.log('good morning, power user')
})
.then('base')
.then(() => {
console.log('hello, base user')
})
.default(() => {
console.log('hello, base user')
})
fcf
.while(true)
.do(user => greetUser.run(user))
.run(user)
The fcf.while
object retains the value returned by its initial control
function
const {value} = fcf
.while(true)
.do(() => {
console.log('hello')
})
.run('hello')
console.log(value) // true
- Provide support for async functions
- Add more control-flow methods