Modernizing Meteor API conventions - some thoughts


#1

The observable specification has been emerging, and it’s standardizing on a few things, which it would be nice if Meteor would support… One of which is that the return value from a subscribe call is an object on which you call unsubscribe, (not ‘stop’ as Meteor currently has it)…

Also, it’d be nice if more Meteor functions were callback-free, lifted up to the level of Promises or Observables.

If you have other examples of friction points for you in the Meteor API, please post a reply and example, so we can have a growing list (for 1.6 / 2.0 perhaps!)


#2

On the one hand, I’m excited that there are now better primitives for stuff like streams of events, and deferred values, in JavaScript. On the other hand, it’s unfortunate that these don’t map nicely onto the underlying implementation. For example:

  • Promises can’t be cancelled, or support callbacks for different ready states. You only get one then.
  • Observables don’t specify what’s supposed to happen when you subscribe multiple times. So it’s not predictable what will happen in that case - should that start two subscriptions to the server, for example?

#3

Yes! I know what you mean

Regarding multiple subscriptions, RxJS has options to control that, via the hot/cold observable construct, and methods like Publish() , RefCount(), etc…

It’s just an open call, I may post more here when my ideas get more concrete.

Dean


#4

I, for one, would like to see existing JavaScript constructs – particularly promises – applied more.

To this end, we’ve wrapped subscribe with a promise. That lets us do an instanceof test before, say, loading a page. We can then wait on the promise or proceed with rendering, depending. We don’t subscribe directly and the superclass that subscribes but returns a promise keeps a cache of subscriptions so we can unsubscribe or supplant as needed.

FWIW, here’s the boiled-down code:

// cb is an object constructed from the last param on the subscription call.
const promise = new Promise(( resolve, reject ) => {
    try {
        var handle = subscribe( path, ...args, {
                onReady : () => {
                    cb.onReady && cb.onReady();
                    // We're refactoring an existing codebase, so we're using 
                    // an event model rather than reactive or observerable vars
                    this.trigger( name + ':ready' );
                    resolve( true );
                },
                onStop : error => {
                    cb.onStop && cb.onStop( error );
                    this.trigger( name + ':stop', error );
                    reject( error );
                }
            });
    } catch( error ){
        reject( error );
    } finally {
        handle = handle || { stop : Function.NOOP };
        handle.argsSignature = argsSignature( args );
        this._subscriptions[path] = handle;
    }
});

// Keep a reference to the promise too.
return this._subscriptions[path].promise = promise;

#5

@sashko – Admittedly, I haven’t dug deeply into React, but what do you mean “different ready states”? You mean like “accepted but pending” vs “finished” or “error”?

I’m having a tough time imagining use cases where my superclass needs to do something different depending on the ready states. I’d think that would be very implementation specific and so naturally coded into any .then and .catch of the subclasses or instances.

Also FWIW, in case we have a bunch of promises stacking up for something like a page load – we use a generator function and Promise.all to track whether everybody’s done and then release for further processing (e.g. rendering).