Last active
July 19, 2019 06:13
-
-
Save torgeir/6d1810d008d5764fcf37d9fc21093904 to your computer and use it in GitHub Desktop.
A plain js take on @ccorcos elmish
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const declare = function (dispatch, state) { | |
const view = function () { | |
if (state.error) { | |
return <p>Error: { state.error }</p>; | |
} | |
if (state.loading) { | |
return <p>Clock: Loading..</p>; | |
} | |
return <p>Clock: { `${state.date} ${state.time}` }</p>; | |
} | |
// first request wins | |
const http = state.loading | |
? [{ | |
key: 'lookup-time', | |
url: 'http://time.jsontest.com/', | |
success: (json) => dispatch('lookup-time-done', { json }), | |
error: (error) => dispatch('lookup-time-error', { error }) | |
}, { | |
key: 'lookup-date', | |
url: 'http://date.jsontest.com/', | |
success: (json) => dispatch('lookup-date-done', { json }), | |
error: (error) => dispatch('lookup-date-error', { error }) | |
}] | |
: []; | |
return { view, http }; | |
}; | |
const initialState = { loading: true }; | |
const update = function (state = initialState, action) { | |
switch (action.type) { | |
case 'lookup-time-error': | |
return { loading: false, error: `lookup-time-error: ${action.error.message || 'an error occurred'}` }; | |
case 'lookup-date-error': | |
return { loading: false, error: `lookup-date-error: ${action.error.message || 'an error occurred'}` }; | |
case 'lookup-time-done': | |
return { loading: false, time: action.json.time, date: action.json.date }; | |
case 'lookup-date-done': | |
return { loading: false, time: action.json.time, date: action.json.date }; | |
} | |
return state; | |
}; | |
const App = { declare, update }; | |
run(debuggable(App), [ | |
render(), | |
fetch(), | |
]); | |
/* inspired by https://github.com/ccorcos/elmish/ */ | |
function run (Component, services = []) { | |
let isThrottled = false; | |
const throttled = function (fn) { | |
isThrottled = true; | |
fn(); | |
isThrottled = false; | |
}; | |
let state; | |
const dispatch = function (type, data) { | |
state = window.state = Component.update(state, { type, ...data }); | |
if (!isThrottled) { | |
const effects = Component.declare(dispatch, state); | |
services.forEach(service => service(effects, throttled)); | |
} | |
}; | |
dispatch(undefined); | |
return { state, dispatch }; | |
} | |
// composable components | |
function debuggable (Component) { | |
const update = function (state, action) { | |
return Component.update(state, action); | |
}; | |
const declare = function (dispatch, state) { | |
const loggingDispatch = function (type, data) { | |
console.log('dispatching', type, data); | |
return dispatch(type, data); | |
}; | |
return Component.declare(loggingDispatch, state); | |
}; | |
return { update, declare }; | |
} | |
// services, for side-effects | |
function render () { | |
return ({ view }) => | |
ReactDom.render(typeof view == 'function' ? view() : view, el); | |
} | |
function fetch () { | |
let pending = []; | |
return function ({ http = [] }) { | |
const staleHttpRequests = Object.keys(pending) | |
.map(key => pending[key]) | |
.filter((req) => http.filter(r => r.key == req.key).length == 0); | |
staleHttpRequests.forEach((req) => | |
console.log(`request ${req.key} is no longer pending and could be aborted`)); | |
const newRequests = http.filter((req) => !(req.key in pending)); | |
pending = http.reduce(function (acc, req) { | |
acc[req.key] = req; | |
return acc; | |
}, {}); | |
newRequests.forEach(function (req) { | |
const { key, url } = req; | |
const stillPending = () => key in pending; | |
window.fetch(url) | |
.then(res => res.json()) | |
.then(function (json) { | |
if (!stillPending()) { | |
return; | |
} | |
delete pending[key]; | |
req.success(json); | |
}) | |
.catch(function (err) { | |
if (!stillPending()) { | |
return; | |
} | |
delete pending[key]; | |
req.error(err); | |
}); | |
}); | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# link to runnable example, copy it into a browser | |
http://omniscientjs.github.io/playground/#const%20declare%20%3D%20function%20(dispatch%2C%20state)%20%7B%0A%20%20%0A%20%20const%20view%20%3D%20function%20()%20%7B%0A%20%20%20%20if%20(state.error)%20%7B%0A%20%20%20%20%20%20return%20%3Cp%3EError%3A%20%7B%20state.error%20%7D%3C%2Fp%3E%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20if%20(state.loading)%20%7B%0A%20%20%20%20%20%20return%20%3Cp%3EClock%3A%20Loading..%3C%2Fp%3E%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20return%20%3Cp%3EClock%3A%20%7B%20%60%24%7Bstate.date%7D%20%24%7Bstate.time%7D%60%20%7D%3C%2Fp%3E%3B%0A%20%20%7D%0A%20%20%0A%20%20%2F%2F%20first%20request%20wins%0A%20%20const%20http%20%3D%20state.loading%0A%20%20%20%20%3F%20%5B%7B%20%0A%20%20%20%20%20%20%20%20key%3A%20'lookup-time'%2C%20%0A%20%20%20%20%20%20%20%20url%3A%20'http%3A%2F%2Ftime.jsontest.com%2F'%2C%0A%20%20%20%20%20%20%20%20success%3A%20(json)%20%3D%3E%20dispatch('lookup-time-done'%2C%20%7B%20json%20%7D)%2C%0A%20%20%20%20%20%20%20%20error%3A%20(error)%20%3D%3E%20dispatch('lookup-time-error'%2C%20%7B%20error%20%7D)%0A%20%20%20%20%20%20%7D%2C%20%7B%20%0A%20%20%20%20%20%20%20%20key%3A%20'lookup-date'%2C%20%0A%20%20%20%20%20%20%20%20url%3A%20'http%3A%2F%2Fdate.jsontest.com%2F'%2C%0A%20%20%20%20%20%20%20%20success%3A%20(json)%20%3D%3E%20dispatch('lookup-date-done'%2C%20%7B%20json%20%7D)%2C%0A%20%20%20%20%20%20%20%20error%3A%20(error)%20%3D%3E%20dispatch('lookup-date-error'%2C%20%7B%20error%20%7D)%0A%20%20%20%20%20%20%7D%5D%0A%20%20%20%20%3A%20%5B%5D%3B%0A%20%20%0A%20%20return%20%7B%20view%2C%20http%20%7D%3B%0A%7D%3B%0A%0A%0Aconst%20initialState%20%3D%20%7B%20loading%3A%20true%20%7D%3B%0Aconst%20update%20%3D%20function%20(state%20%3D%20initialState%2C%20action)%20%7B%0A%20%20%0A%20%20switch%20(action.type)%20%7B%0A%20%20%20%20case%20'lookup-time-error'%3A%20%0A%20%20%20%20%20%20return%20%7B%20loading%3A%20false%2C%20error%3A%20%60lookup-time-error%3A%20%24%7Baction.error.message%20%7C%7C%20'an%20error%20occurred'%7D%60%20%7D%3B%0A%0A%20%20%20%20case%20'lookup-date-error'%3A%20%0A%20%20%20%20%20%20return%20%7B%20loading%3A%20false%2C%20error%3A%20%60lookup-date-error%3A%20%24%7Baction.error.message%20%7C%7C%20'an%20error%20occurred'%7D%60%20%7D%3B%0A%20%20%0A%20%20%20%20case%20'lookup-time-done'%3A%20%0A%20%20%20%20%20%20return%20%7B%20loading%3A%20false%2C%20time%3A%20action.json.time%2C%20date%3A%20action.json.date%20%7D%3B%0A%0A%20%20%20%20case%20'lookup-date-done'%3A%20%0A%20%20%20%20%20%20return%20%7B%20loading%3A%20false%2C%20time%3A%20action.json.time%2C%20date%3A%20action.json.date%20%7D%3B%0A%20%20%7D%0A%20%20%0A%20%20return%20state%3B%0A%7D%3B%0A%0A%0Aconst%20App%20%3D%20%7B%20declare%2C%20update%20%7D%3B%0A%0A%0Arun(debuggable(App)%2C%20%5B%0A%20%20render()%2C%0A%20%20fetch()%2C%0A%5D)%3B%0A%0A%0A%2F*%20inspired%20by%20https%3A%2F%2Fgithub.com%2Fccorcos%2Felmish%2F%20*%2F%0Afunction%20run%20(Component%2C%20services%20%3D%20%5B%5D)%20%7B%0A%0A%20%20let%20isThrottled%20%3D%20false%3B%0A%20%20const%20throttled%20%3D%20function%20(fn)%20%7B%0A%20%20%20%20isThrottled%20%3D%20true%3B%0A%20%20%20%20fn()%3B%0A%20%20%20%20isThrottled%20%3D%20false%3B%0A%20%20%7D%3B%0A%0A%20%20let%20state%3B%0A%0A%20%20const%20dispatch%20%3D%20function%20(type%2C%20data)%20%7B%0A%20%20%20%20state%20%3D%20window.state%20%3D%20Component.update(state%2C%20%7B%20type%2C%20...data%20%7D)%3B%0A%20%20%20%20if%20(!isThrottled)%20%7B%0A%20%20%20%20%20%20const%20effects%20%3D%20Component.declare(dispatch%2C%20state)%3B%0A%20%20%20%20%20%20services.forEach(service%20%3D%3E%20service(effects%2C%20throttled))%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20dispatch(undefined)%3B%0A%0A%20%20return%20%7B%20state%2C%20dispatch%20%7D%3B%0A%7D%0A%0A%0A%2F%2F%20composable%20components%0A%0Afunction%20debuggable%20(Component)%20%7B%0A%0A%20%20const%20update%20%3D%20function%20(state%2C%20action)%20%7B%0A%20%20%20%20return%20Component.update(state%2C%20action)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20declare%20%3D%20function%20(dispatch%2C%20state)%20%7B%0A%20%20%20%20const%20loggingDispatch%20%3D%20function%20(type%2C%20data)%20%7B%0A%20%20%20%20%20%20console.log('dispatching'%2C%20type%2C%20data)%3B%0A%20%20%20%20%20%20return%20dispatch(type%2C%20data)%3B%0A%20%20%20%20%7D%3B%0A%20%20%20%20return%20Component.declare(loggingDispatch%2C%20state)%3B%0A%20%20%7D%3B%0A%0A%20%20return%20%7B%20update%2C%20declare%20%7D%3B%0A%7D%0A%0A%0A%2F%2F%20services%2C%20for%20side-effects%0A%0Afunction%20render%20()%20%7B%0A%20%20return%20(%7B%20view%20%7D)%20%3D%3E%0A%20%20%20%20ReactDom.render(typeof%20view%20%3D%3D%20'function'%20%3F%20view()%20%3A%20view%2C%20el)%3B%0A%7D%0A%0Afunction%20fetch%20()%20%7B%0A%0A%20%20let%20pending%20%3D%20%5B%5D%3B%0A%0A%20%20return%20function%20(%7B%20http%20%3D%20%5B%5D%20%7D)%20%7B%0A%0A%20%20%20%20const%20staleHttpRequests%20%3D%20Object.keys(pending)%0A%20%20%20%20%20%20.map(key%20%3D%3E%20pending%5Bkey%5D)%0A%20%20%20%20%20%20.filter((req)%20%3D%3E%20http.filter(r%20%3D%3E%20r.key%20%3D%3D%20req.key).length%20%3D%3D%200)%3B%0A%0A%20%20%20%20staleHttpRequests.forEach((req)%20%3D%3E%0A%20%20%20%20%20%20console.log(%60request%20%24%7Breq.key%7D%20is%20no%20longer%20pending%20and%20could%20be%20aborted%60))%3B%0A%0A%20%20%20%20const%20newRequests%20%3D%20http.filter((req)%20%3D%3E%20!(req.key%20in%20pending))%3B%0A%0A%20%20%20%20pending%20%3D%20http.reduce(function%20(acc%2C%20req)%20%7B%0A%20%20%20%20%20%20acc%5Breq.key%5D%20%3D%20req%3B%0A%20%20%20%20%20%20return%20acc%3B%0A%20%20%20%20%7D%2C%20%7B%7D)%3B%0A%0A%20%20%20%20newRequests.forEach(function%20(req)%20%7B%0A%20%20%20%20%20%20const%20%7B%20key%2C%20url%20%7D%20%3D%20req%3B%0A%0A%20%20%20%20%20%20const%20stillPending%20%3D%20()%20%3D%3E%20key%20in%20pending%3B%0A%0A%20%20%20%20%20%20window.fetch(url)%0A%20%20%20%20%20%20%20%20.then(res%20%3D%3E%20res.json())%0A%20%20%20%20%20%20%20%20.then(function%20(json)%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(!stillPending())%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20delete%20pending%5Bkey%5D%3B%0A%20%20%20%20%20%20%20%20%20%20req.success(json)%3B%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20.catch(function%20(err)%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(!stillPending())%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20delete%20pending%5Bkey%5D%3B%0A%20%20%20%20%20%20%20%20%20%20req.error(err)%3B%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%3B%0A%7D%3B%0A |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment