Skip to content
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

Add a built-in component for async view loading #2282

Open
dead-claudia opened this issue Nov 4, 2018 · 5 comments
Open

Add a built-in component for async view loading #2282

dead-claudia opened this issue Nov 4, 2018 · 5 comments
Labels
Type: Enhancement For any feature request or suggestion that isn't a bug fix

Comments

@dead-claudia
Copy link
Member

dead-claudia commented Nov 4, 2018

Updates

I'm thinking we could add a new component for async view loading, for things like lazy component initialization based on app state, database loading, and reducing the need for route resolvers (which I'm proposing on partially removing in #2278).

Here's my proposed API:

m(Async, {
	// Use this to generate the promise data.
	init: function (signal) {
		const request = requestSomeData()
		signal.onabort = () => request.cancel()
		return request.promise()
	},

	// This returns the view to render while the promise is pending.
	pending: function () {
		// ...
	},

	// This returns the view to render with the resolved promise data, in case
	// of resolution
	ready: function (resolvedData) {
		// ...
	},

	// This returns the view to render with any rejected promise error, in case
	// of rejection
	error: function (rejectedError) {
		// ...
	},
})

If you want to reinitialize it, just wrap it in a keyed fragment and pass a new key.

Implementation-wise, it's pretty simple, and could be implemented trivially in userland.

This, of course, is slightly more complicated, but this has the semantics I'd prefer to see mod sync handling of non-promise return/throw.

Note that this depends on #2074 (comment).

// v2
function Async(v) {
	let method = "pending"
	const ctrl = new AbortController()
	let data
	new Promise(resolve => resolve(v.attrs.init(ctrl.signal))).then(
		d => { method = "ready"; data = d; m.redraw() },
		e => { method = "error"; data = e; m.redraw() }
	)
	return {
		view(v) {
			let view = v.attrs[method](
				method === "pending" ? undefined : data
			)
			if (!Array.isArray(view)) view = [view]
			return m.fragment({key: method}, view)
		},
		onremove() {
			if (method === "pending") ctrl.abort()
		},
	}
}

// After #2689
function Async(v) {
	let method = "pending"
	const ctrl = new AbortController()
	let data
	new Promise(resolve => resolve(v.attrs.init(ctrl.signal))).then(
		d => { method = "ready"; data = d; m.redraw() },
		e => { method = "error"; data = e; m.redraw() }
	)
	return v => {
		let view = v.attrs[method](
			method === "pending" ? undefined : data
		)
		if (!Array.isArray(view)) view = [view]
		return m.fragment({
			key: method,
			afterRemove: () => { if (method === "pending") ctrl.abort() }
		}, view)
	}
}

// After #2278 + #2295
function Async(v, o, [method, data] = ["pending", undefined], update) {
	if (o == null) {
		const ctrl = new AbortController()
		new Promise(resolve => resolve(v.attrs.init(ctrl.signal))).then(
			d => update(["ready", d]),
			e => update(["error", e])
		)
		data = () => ctrl.abort()
	}
	return {
		next: [method, data],
		onremove: method === "pending" ? data : undefined,
		view: m(m.keyed, m(m.fragment, {key: method}, v.attrs[method](
			method === "pending" ? undefined : data
		))),
	}
}
@dead-claudia dead-claudia added the Type: Enhancement For any feature request or suggestion that isn't a bug fix label Nov 4, 2018
@dead-claudia dead-claudia added this to the post-v2 milestone Nov 4, 2018
@dead-claudia dead-claudia added this to Under consideration in Feature requests/Suggestions via automation Nov 4, 2018
@project-bot project-bot bot added this to Needs triage in Triage/bugs Nov 4, 2018
@dead-claudia dead-claudia removed this from Needs triage in Triage/bugs Nov 4, 2018
@StephanHoyer
Copy link
Member

On the first thought: I see this in userland.

@barneycarroll
Copy link
Member

barneycarroll commented Nov 4, 2018

I see this in userland.

I agree. Promises can be difficult to deal with declaratively, but ultimately this isn't something that involves any Mithril-specific logic, other than a component to store state. I wrote this component to demonstrate the potential of nested vtree functions - Promiser - which is a simple component that consumes a promise and exposes an explosion of promise state to the consumed view function. Little demo here, line 39 onwards. IMO this is a lot neater - less OO, more obvious state exposure - but I don't think it belongs in core.

@dead-claudia
Copy link
Member Author

It's not really mandatory to have in core, and I addressed the primary use of route resolvers (auth) with a much better-suited abstraction. Would you all be okay with an out-of-core-but-officially-blessed mithril/async?

This is probably the least significant facet of #2278, and I wouldn't have an issue separating this from that.

@StephanHoyer
Copy link
Member

not bundled in core but opt in module would be great thought

@dead-claudia
Copy link
Member Author

@StephanHoyer Yeah, I'm starting to agree with that thought. I'll remove it from my list in #2278 and just keep it with my (newly filed) #2284.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Enhancement For any feature request or suggestion that isn't a bug fix
Projects
Development

No branches or pull requests

3 participants