// trnsitionRequests -> BrowserAPI -> [ resolveRoute -> loadData -> render -> restoreScroll ]
// poor man's strem.flatMapLatest()
router.pipe((payload, next) => {
next(1)
next(2)
return () => {
// cleanup
}
})
createRouter(callback => {
browserHistoryAPI.listen(callback)
})
.pipe(createNestedReactResolver(...))
.pipe(createReactDataLoader(...))
.pipe(createNestedReactRenderer(...))
.pipe(createScrollRestorer(...))
Last active
May 4, 2016 22:34
-
-
Save rpominov/acb11abfb5383be6db70 to your computer and use it in GitHub Desktop.
Router concept
function createPipeline(...handlers) {
handlers.reverse()
return handlers.reduce((next, handler) => {
var clear = () => {}
var latestPayload
return payload => {
clear()
latestPayload = payload
clear = handler(payload, x => {
if (latestPayload !== payload) {
console.warn('calling next after clear')
return
}
next(x)
})
}
}, () => {})
}
function map(fn) {
return (payload, next) => {
next(fn(payload))
return () => {}
}
}
function repeat(payload, next) {
var id = setInterval(() => {next(payload)}, 1000)
return () => {clearInterval(id)}
}
// Usage
something.listen(createPipeline(
map(x => x * 2),
repeat,
map(x => console.log(x))
))
foo.pipe().pipe()
is a bad idea, because we have to care about foo1 = foo.pipe(bar); foo2 = foo1.pipe(baz1); foo3 = foo1.pipe(baz2)
case. Better if we require that all handlers must be provided up front.
Clear :: () => void
Next :: payload => void
Stream :: Next => Clear | Rx.Stream | Bacon.Stream | Whatever.Stream...
Handler :: payload => Stream
createPipeline :: [Handler] => Next
function createPipeline(...handlers) {
handlers.reverse()
return handlers.reduce((next, handler) => {
let dispose = () => {}
return payload => {
dispose()
dispose = subscribe(handler(payload), next)
}
}, () => {})
}
function subscribe(stream, subscriber) {
const dispose = stream(payload => {
if (subscriber === null) {
console.warn('calling next after clear')
return
}
subscriber(payload)
})
return () => {
subscriber = null
dispose()
}
}
/* Normaly we can subscribe to a stream by simply doing:
*
* conts unsub = stream(callback)
*
* But we have to trust `stream` that it won't call the `callback`
* after `unsub` has been called. This function fixes the issue.
*/
function subscribe(stream, callback) {
const dispose = stream(payload => {
if (callback === null) {
console.warn('calling next after clear')
return
}
callback(payload)
})
return () => {
callback = null
dispose()
}
}
function flatMapLatest(stream, handler) {
return sink => {
let disposeSpawned = () => {}
const disposeMain = subscribe(stream, payload => {
disposeSpawned()
disposeSpawned = subscribe(handler(payload), sink)
})
return () => {
disposeSpawned()
disposeMain()
}
}
}
function createPipeline(...handlers) {
let _sink
const initialStream = sink => {
_sink = sink
return () => {}
}
const resultStream = handlers.reduce(flatMapLatest, initialStream)
resultStream(() => {})
return _sink
}
Possible parts
- Matcher: https://github.com/troch/path-parser + ?
- History API: https://github.com/rackt/history
<Link>
: ?- Scroll: https://github.com/rackt/scroll-behavior ?
- Glue: https://github.com/rpominov/basic-streams ?
- Render nested react components: do we even need nested?
- Data loading: out of scope?
source → flatMapLatest → flatMapLatest → flatMapLatest → handler
Event = Normal | Error | Redirect | End
fmlNormal, fmlError, fmlRedirect, fmlEnd (?)
- on server source emits a
Normal
and anEnd
, then we wait forEnd
on other side and use the previousEvent
fml(a -> Stream<b,c,d> | Event<b,c,d>)
Event = {type: ... | 'END', payload: any}
Middleware = Event -> Event | Stream<Event> | Array<Event>
{type: 'END'}
is special Event, we automatically dispose subscription after it. Other types like 'NORMAL', 'ERROR', 'REDIRECT', etc. are up to user (or maybe not, we might need a convention for example if we provide middlewares out of the box, but only END is special anyway)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Open questions:
Redirects
Errors handling
Active links (is this the only thing for which we might need context, btw?)