-
Notifications
You must be signed in to change notification settings - Fork 779
Make app return kill func #873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
@@ Coverage Diff @@
## master #873 +/- ##
==========================================
- Coverage 19.45% 19.25% -0.21%
==========================================
Files 1 1
Lines 185 187 +2
==========================================
Hits 36 36
- Misses 149 151 +2
Continue to review full report at Codecov.
|
Added |
Actually, let’s forget about those last two commits. Sorry! I’ll revert them first thing tomorrow. Short story: memory is only kept for as long as reference to kill function is kept. So no need to null the node. |
@zaceno Could you elaborate on how and why this works? Also, can we always cut subscriptions off like this or would we benefit by having something like: const Goodbye = (state) => ({ ...state, isRunning: false })
const kill = app({
node,
init,
done: Goodbye,
subscriptions: (state) => [
state.isRunning && [
ping,
{
host,
gateway,
timeout,
onRequestResponse,
},
],
],
}) Finally, what does |
Sure! Ok, so what the point of this is, is to basically make an app stop doing anything and to make it stop being interactive. We do want to leave the nodes as they are, because we might want to use them for something (like perhaps resurrecting the app). If we want to remove the nodes that's easy enough in userland. The stuff this PR does is not doable in userland. First, we want to make sure that we can't interact with the elements in the app. Rather than rerendering or removing all the event handlers we simply set Furthermore this makes sure that effects and subscriptions that get the idea to dispatch something after the app has been killed, won't cause runtime errors. But subscriptions also typically hold on to their event-listeners. If you are starting and stopping apps a lot like in a customElement scenario, that's a memory leak. So we also want to make sure to unsubscribe everuthing (even though nothing can be dispatched). That's what As for the |
Oh wait I see. You want to do something when the app stops? In principle, shouldn't that be an effect (=output) rather than a subscription (=input) ? At least if you're doing it from inside the app. If you're doing it from outside, you could do it with vanilla js |
How important is it we Here are some ideas. No changes to core, but we need an id so that my import { app } from "hyperapp"
import { end, kill } from "./fx"
import { initialState } from "./state"
const Done = (state) => ({ ...state, isRunning: false })
app({
init: (defaultProps = {}) => ({ ...defaultProps, ...initialState }),
subscriptions: (state) => [
[end, { id: 666, Done }],
state.isRunning &&
[
/*...*/
],
],
})
// Moments later...
kill(666) Or with minimal changes to core, import { app } from "hyperapp"
import { end, kill } from "./fx"
import { initialState } from "./state"
const Done = (state) => ({ ...state, isRunning: false })
const id = app({
init: (defaultProps = {}, id) => ({ ...defaultProps, ...initialState, id }),
subscriptions: (state) => [
[end, { id, Done }],
state.isRunning &&
[
/*...*/
],
],
})
// Moments later...
kill(id) Or simpler, but riskier, just return import { app } from "hyperapp"
import { initialState } from "./state"
const Done = (state) => ({ ...state, isRunning: false })
const dispatch = app({
init: (defaultProps = {}) => ({ ...defaultProps, ...initialState }),
subscriptions: (state) => [
[end, { Done }],
state.isRunning &&
[
/*...*/
],
],
})
// Moments later...
dispatch(Done) |
dispatch=Function Is the “cause of death” that is how we effectively kill the app. Even any outstanding fetch-calls that come back with data will not cause any “death spasms” with that. Your approach doing it all in userland all requires the app to work through one last render which doesn’t work in the scenario of customElements (which is what I was working on when I came up with this). There we can’t let the app render one final time because the node has been removed from the dom and weirdness ensues. That’s why dispatch=Function. To make absolutely sure nothing tries to modify the DOM once the kill function is called. Also, since in your solution, you do not disable dispatch, any effects waiting for something before they dispatch an action will cause “haunting” (the app will do something even after its supposed to be dead) |
Can I bump this for consideration, since there aren't any plans (afaik?) to return anything from Being able to kill an app cleanly and immediately is useful when using a multi-app architecture, and when implementing custom elements. Yes it is possible to use middleware + HOA emulate this but it requires an extra render (since we can't get att the Those things might be considered edge cases but whatever: I feel that Hyperapp is incomplete if it can't clean up after itself without relying on the DOM, basically. |
@zaceno Do you have a link to a code example that would rely on a |
@jorgebucaran sure! That custom element pen isn't working anyway anymore, but I stripped down some code I was working on at the moment, to some examples that demonstrate the problems with stopping apps currently. Right now I'm working on an update to my sevenguis examples, and I wanted to make it like an app gallery (also as a proof of concept of how to do that in general). First have a look at this example. It is a simple gallery which lets you run a counter or a clock. The clock has a subscription to Clearly we need the ability to stop an app when we switch a way from it. So in this example I use a custom But now I am getting errors each time I switch ( I attempt to fix that in this third example by making my custom app function handle the stopped state, and render a single, empty I can only conclude that properly stopping an app "from the outside" is not feasible, we need hyperapp to support a way to do that without causing an extra render. |
A variation of this proposal would be to modify + : action == null
+ ? patchSubs(subs, EMPTY_ARR, (dispatch = id))
: setState(action)
) I've always felt like we should probably return With Userland |
CLosing in favor of #1018 |
1. Return dispatch so it could be used for tooling, automation, tests, and so on. 2. Make dispatch with no arguments stop the app (#873). Stopping an app means that future calls to dispatch do nothing and all active subscriptions are unsubscribed.
Main idea: make it possible for apps to die like things usually do: the corpse (the DOM nodes) are still there, but do nothing. Subscriptions are shut off as well.
usage:
There was a thought to return an object, where
kill
would be the only prop, as a way of future-proofing (in case we want to add other things to app's return in the future, without breaking existing code).I think it's a good idea but could use some discussion, so for this initial submission I decided to just return the kill function, sidestepping the naming discussion and keeping the bundle-size addition minimal.