Skip to content

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

Closed
@dead-claudia

Description

@dead-claudia
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
		))),
	}
}

Metadata

Metadata

Assignees

Labels

Area: CoreFor anything dealing with Mithril core itselfType: EnhancementFor any feature request or suggestion that isn't a bug fix

Type

No type

Projects

Status

Completed/Declined

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions