tc39 / proposal-observable

Observables for ECMAScript
https://tc39.github.io/proposal-observable/
3.06k stars 90 forks source link

Symbol.observable for built-in objects? #181

Open alinnert opened 6 years ago

alinnert commented 6 years ago

I couldn't find anything on this topic. Does this proposal also cover implementing Symbol.observable in existing objects?

I'd like to see it implemented on Promise.prototype for example. This way you could convert any Promise into an Observable. Similar to RxJS' Observable.fromPromise(new Promise(...)):

Observable.from(new Promise(...))
// or
Observable.from(functionThatReturnsAPromise())

Typical use case:

function makeAjaxCall() {
  return Observable.from(fetch(...))
}

buttonClickObservable
  .flatMap(makeAjaxCall())

flatMap might be implemented manually or later, just like map and filter.

zenparsing commented 6 years ago

This seems good to me but we'll have to explore tradeoffs.

benlesh commented 6 years ago

FWIW: I thought the purpose of from was to be able to identify things like thennables, iterables, et al and convert them to Observables. That's what we're doing in RxJS

hitmands commented 6 years ago

I think it would make sense to allow any object to be consumed by Observable.from as long as it implements a specific interface, and Symbol.observable looks like the right way, just like Symbol.iterator for iterable objects.

benlesh commented 6 years ago

Given that Array.from doesn't require a single specific interface (It will handle both Iterables and ArrayLike), I don't think Observable.from should either. Observable from should work for Symbol.observable interface, Symbol.iterator interface and thennables (Promises) at the very least. Probably should work with Symbol.asyncIterator as well.

zenparsing commented 6 years ago

@benlesh I'm not sure about thenables. From a language (rather than library) standpoint, I think it makes more sense to stick to our "observable or iterable" generic interfaces.

benlesh commented 6 years ago

@zenparsing I'm with you, it would be great if all of these types standardized on symbol interfaces. But Promise hasn't. And Array.from doesn't. It's probably good to stay consistent, because anything we can convert to an Array, should be convertible to an Observable.

The precedent is that Array takes { length: number } interface. I think that function arguments has Symbol.iterator implemented now (as a spec), but Array still supports { length }.

Currently RxJS supports:

  1. Symbol.observable
  2. Symbol.iterator
  3. Array
  4. { length: number, [key: number]: any } "ArrayLike"
  5. { then: Function } "thennable" (yay, the Promise API)

With plans to support Symbol.asyncIterator

benjamingr commented 6 years ago

Note that the non-symbol interfaces simply predate symbols - there is no magical reason there is no promise "symbol". I do however think Observable.from should work with thennables since the usage is common :)

zenparsing commented 6 years ago

Array.from solves a very array-specific problem: converting iterables or array-likes into arrays. We don't need to solve this array-specific problem in Observable.from. Users can do:

let observable = Observable.from(Array.from({ length: 1, 0: 'value' }))

Likewise with non-Promise "thenables":

let observable = Observable.from(Promise.resolve(thenable))

It's more flexible and maintainable to stick to the generic symbol interfaces unless there's a good reason not to.

benlesh commented 6 years ago

Likewise with non-Promise "thenables":

let observable = Observable.from(Promise.resolve(thenable))

I'm out of the loop maybe... What would Observable.from check for if not "thennability"? instanceof Promise? Wouldn't that mean it wouldn't work with Bluebird, RSVP, et al?

Is someone planning on proposing Symbol.promise somewhere? If so, I'm all for it, honestly. I think symbols are great for interop.

benjamingr commented 6 years ago

Is someone planning on proposing Symbol.promise somewhere? If so, I'm all for it, honestly. I think symbols are great for interop.

I don't think so - honestly then works pretty great. I'm also in favor of checking for then rather than instanceof Promise for Symbol.observable (languages like C# that have private methods and nominal types have also opted for the same structural interface with GetAwaiter vs is Task)

benlesh commented 6 years ago

I think it would be cool if we reclaimed then as a usable method name, personally... but that's me.

zenparsing commented 6 years ago

What would Observable.from check for if not "thennability"? instanceof Promise? Wouldn't that mean it wouldn't work with Bluebird, RSVP, et al?

Well, I was thinking that Promise.prototype would have a Symbol.observable method, but perhaps it would be better to avoid any circularity between Promise and Observable.