Skip to content

Instantly share code, notes, and snippets.

@milankinen
Last active February 8, 2017 00:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milankinen/f87821926b18963b2abb to your computer and use it in GitHub Desktop.
Save milankinen/f87821926b18963b2abb to your computer and use it in GitHub Desktop.
Bacon flatUpdate
const Bacon = require("baconjs")
/**
* flatUpdate :: (state, [Observable+, [(state,...events)=>Observable<A>, (state, A)=>newState]]+) => Observable<state>
*
* const stateP = flatUpdate(initialState,
* [event1S], (state, newState) => newState, // supports normal Bacon.update
* [event2S], (state, newState) => Bacon.later(100, newState) // supports delayed state updating
* [event3S], [submitForm, handleSubmitResult] // supports 2-stage state updating
* )
*
* function submitForm(state, event) {
* return Bacon.later(1000, doSomethingWith(state, event)) // simulate "server"
* }
* function handleSubmitResult(state, resultFromServer) {
* return {...state, ...resultFromServer} // resultFromServer === doSomething(state, event)
* }
*/
function flatUpdate(initial, ...patterns) {
const updateBus = new Bacon.Bus(),
lazyUpdate = [updateBus, (state, update) => update(state)]
const streams = patterns.filter((_, i) => i % 2 === 0),
callbacks = patterns.filter((_, i) => i % 2 !== 0).map(toUpdateHandler),
args = streams.map((_, i) => [streams[i], callbacks[i]]).reduce((memo, pair) => [...memo, ...pair], lazyUpdate)
return Bacon.update(...[initial, ...args])
function toUpdateHandler(cb) {
if (cb instanceof Array) {
if (cb.length !== 2) {
throw new Error("Update pattern must be [Function<(state, ...args), Observable<A>>, Function<(state, A), state>")
}
return (state, ...args) => {
const [fetch, resolve] = cb
const result = fetch(state, ...args)
if (result instanceof Bacon.Observable) {
updateBus.plug(result.map(val => state => resolve(state, val)))
return state
} else {
return result
}
}
} else {
return (state, ...args) => {
const result = cb.apply(null, [state, ...args])
if (result instanceof Bacon.Observable) {
updateBus.plug(result.map(newState => () => newState))
return state
} else {
return result
}
}
}
}
}
const Bacon = require("baconjs"),
{expect} = require("chai")
describe("flatUpdate", () => {
it("makes server integration easier", done => {
const userS = Bacon.once({name: "mla", status: "guest"}),
submitS = Bacon.once({form: "login"})
const stateP = flatUpdate({},
[userS], (state, user) => ({...state, user}),
[submitS], [loginToServer, handleLoginResponse]
)
stateP
.changes()
.bufferWithCount(3)
.take(1)
.onValue(states => {
expect(states).to.deep.equal([
{ user: { name: "mla", status: "guest" } }, // after userS
{ user: { name: "mla", status: "guest" } }, // after loginToServer
{ user: { name: "mla", status: "member" } } // after handleLoginResponse
])
done()
})
function loginToServer(state) {
//console.log("logging in user", state.user.name)
return Bacon.later(100, {status: "ok"})
}
function handleLoginResponse(state, {status}) {
const {user} = state
return status === "ok" ? {...state, user: {...user, status: "member"}} : state
}
})
it("just works", done => {
const ready = Bacon.later(300, 2),
set = Bacon.later(100, 1),
go = Bacon.later(200, 4)
const stateP = flatUpdate(10,
[ready, set, go], [(_, r, s, g) => Bacon.later(100, r + s + g), (state, sum) => state + sum],
[go.map(g => g + 1)], (state, g) => state - g
)
stateP
.changes()
.skipDuplicates()
.bufferWithCount(2)
.take(1)
.onValue(([afterGo, afterAll]) => {
expect(afterGo).to.equal(10 - (4 + 1))
expect(afterAll).to.equal(10 - (4 + 1) + 2 + 1 + 4)
done()
})
})
it("supports fancy things", done => {
const times = Bacon.once(3)
const stateP = flatUpdate([],
[times], [(_, t) => Bacon.interval(10, "tsers").take(t), (state, s) => [...state, s]]
)
stateP
.changes()
.bufferWithCount(4)
.take(1)
.onValue(words => {
expect(words).to.deep.equal([[], ["tsers"], ["tsers", "tsers"], ["tsers", "tsers", "tsers"]])
done()
})
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment