choojs / choo

:steam_locomotive::train: - sturdy 4kb frontend framework
https://choo.io/
MIT License
6.78k stars 595 forks source link

New feature: Async route support #708

Open vgel opened 4 years ago

vgel commented 4 years ago

Hi, Really liking Choo so far! One thing I was missing was the ability to define an async route (I'm working on a game and wanted to preload assets). I didn't see a simple way to accomplish this (maybe I missed something obvious), so I wrote a little module that augments app with an asyncRoute method:

const app = withAsyncRoute(choo());

app.asyncRoute('/', loadResources,
    () => html`<div id="root"><h1>Loading...</h1></div>`, 
    (state, emit, data) => html`<div id="root">Loaded ${data}</div>`
    (state, emit, err, params) => {
        console.error('error loading /:', params, err, state);
        return html`
            <div id="root">
                <h1 style="color: red">Error loading / (see console)</h1>
            </div>
        `;
    },
);

Was wondering if there's interest in either merging this function into Choo proper, or as a third-party npm module?

For reference, here's the module -- it's pretty simple:

module.exports = (app) => Object.assign(app, {
    /**
     * @param  {string} route
     * @param  {Promise<Data>|Function<Promise<Data>>}       promise
     * @param  {Function<State, Emitter, RouteParams>}       loadingHandler
     * @param  {Function<State, Emitter, Data, RouteParams>} loadedHandler
     * @param  {Function<State, Emitter, *, RouteParams>}    errorHandler
     */
    asyncRoute: (route, promise, loadingHandler, loadedHandler, errorHandler) => {
        app.use((state, emitter) => {
            const emit = emitter.emit.bind(emitter);

            app.router.on(route, (params) => {
                state.params = params;

                if (typeof promise === 'function') {
                    promise = promise();
                }

                let completed = false;
                let isError = false;
                let data = null;

                promise.then(result => {
                    completed = true;
                    data = result;
                    emitter.emit('render');
                }).catch(err => {
                    completed = isError = true;
                    data = err;
                    emitter.emit('render');
                });

                return () => {
                    if (!completed) {
                        return loadingHandler(state, emit, params);
                    } else if (isError) {
                        return errorHandler(state, emit, data, params);
                    } else {
                        return loadedHandler(state, emit, data, params);
                    }
                };
            });
        });
    }
});
blahah commented 4 years ago

Related: async event messaging for choo. Beware: here be dragons.