Convert Figma logo to code with AI

tc39 logoproposal-observable

Observables for ECMAScript

3,062
90
3,062
47

Top Related Projects

30,609

A reactive programming library for JavaScript

2,375

An extremely intuitive, small, and fast functional reactive stream library for JavaScript

1,559

👜 A standard for JS callbacks that enables lightweight observables and iterables

3,495

Ultra-high performance reactive programming

Quick Overview

The tc39/proposal-observable repository contains a proposal for adding Observables to JavaScript as a new primitive for managing streams of asynchronous events. This proposal aims to standardize a pattern that has become increasingly common in modern web development, particularly in reactive programming paradigms.

Pros

  • Provides a standardized way to handle asynchronous event streams
  • Improves code readability and maintainability for complex asynchronous operations
  • Aligns with existing patterns in popular libraries like RxJS
  • Enhances JavaScript's capabilities for reactive programming

Cons

  • Adds complexity to the language, potentially increasing the learning curve for developers
  • May overlap with existing asynchronous patterns like Promises and async/await
  • Implementation details and browser support could vary, leading to potential inconsistencies
  • Could lead to overuse in situations where simpler patterns would suffice

Code Examples

  1. Creating a simple Observable:
const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});
  1. Subscribing to an Observable:
observable.subscribe({
  next(x) { console.log('Got value ' + x); },
  error(err) { console.error('Something wrong occurred: ' + err); },
  complete() { console.log('Done'); }
});
  1. Creating an Observable from an event:
const clickObservable = new Observable(subscriber => {
  document.addEventListener('click', event => subscriber.next(event));
});

clickObservable.subscribe(event => console.log('Clicked at: ', event.clientX, event.clientY));

Getting Started

As this is a proposal and not yet implemented in JavaScript engines, you can't directly use Observables in vanilla JavaScript. However, you can experiment with similar concepts using libraries like RxJS:

  1. Install RxJS:
npm install rxjs
  1. Import and use Observables:
import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next('Hello');
  subscriber.next('World');
  subscriber.complete();
});

observable.subscribe({
  next(x) { console.log(x); },
  complete() { console.log('Finished'); }
});

Note that the final implementation may differ from this example if the proposal is accepted and implemented in JavaScript.

Competitor Comparisons

30,609

A reactive programming library for JavaScript

Pros of rxjs

  • Extensive set of operators for complex data transformations
  • Well-established library with a large community and ecosystem
  • Supports multiple programming paradigms (functional, object-oriented)

Cons of rxjs

  • Steeper learning curve due to its comprehensive API
  • Larger bundle size, which may impact performance in some applications
  • More opinionated approach to reactive programming

Code Comparison

proposal-observable:

const observable = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.complete();
});
observable.subscribe(console.log);

rxjs:

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.complete();
});
observable.subscribe(console.log);

Key Differences

  • proposal-observable aims to provide a standardized, minimal Observable implementation
  • rxjs offers a more feature-rich and flexible approach to reactive programming
  • proposal-observable focuses on core functionality, while rxjs includes additional utilities and operators

Use Cases

  • proposal-observable: Ideal for projects requiring a lightweight, standardized Observable implementation
  • rxjs: Better suited for complex applications with advanced reactive programming needs

Community and Adoption

  • proposal-observable: Part of the TC39 process, potentially becoming a native JavaScript feature
  • rxjs: Widely adopted in the Angular ecosystem and other JavaScript projects
2,375

An extremely intuitive, small, and fast functional reactive stream library for JavaScript

Pros of xstream

  • Smaller and more lightweight than Observable
  • Designed specifically for functional reactive programming
  • Provides "hot" observables by default, which can be more efficient in certain scenarios

Cons of xstream

  • Less widely adopted compared to Observable
  • Fewer operators and utility functions out of the box
  • May require additional learning for developers familiar with RxJS or other Observable implementations

Code Comparison

xstream:

import xs from 'xstream';

const stream = xs.periodic(1000).map(i => i * 2);
stream.addListener({
  next: value => console.log(value),
  error: err => console.error(err),
  complete: () => console.log('Done'),
});

proposal-observable:

const observable = new Observable(observer => {
  const interval = setInterval(() => {
    observer.next(Date.now());
  }, 1000);
  return () => clearInterval(interval);
});
observable.subscribe(value => console.log(value));

Both xstream and proposal-observable provide ways to create and work with streams of data. xstream focuses on simplicity and performance, while proposal-observable aims to standardize the Observable pattern across JavaScript environments. The choice between them depends on specific project requirements and developer preferences.

1,559

👜 A standard for JS callbacks that enables lightweight observables and iterables

Pros of Callbag

  • Lightweight and minimalistic approach
  • Flexible and protocol-based, allowing for easy customization
  • Can be used for both push and pull-based streams

Cons of Callbag

  • Less standardized and less widely adopted
  • Steeper learning curve due to its unique approach
  • Fewer built-in operators compared to Observables

Code Comparison

Proposal-observable:

const observable = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.complete();
});
observable.subscribe(x => console.log(x));

Callbag:

const source = (type, data) => {
  if (type === 0) data(0, (t, d) => t === 1 && d(1));
  if (type === 1) data(1); data(2); data(2);
};
pipe(source, forEach(x => console.log(x)));

Key Differences

  1. Syntax: Proposal-observable uses a more traditional object-oriented approach, while Callbag employs a functional style.
  2. Flexibility: Callbag's protocol allows for more diverse use cases, including both push and pull-based streams.
  3. Standardization: Proposal-observable aims to become a standardized part of JavaScript, potentially leading to wider adoption and better ecosystem support.
  4. Learning curve: Proposal-observable may be easier for developers familiar with similar libraries, while Callbag introduces a unique concept that might take more time to grasp.
  5. Ecosystem: Proposal-observable benefits from a larger ecosystem of operators and tools, while Callbag's ecosystem is smaller but growing.
3,495

Ultra-high performance reactive programming

Pros of most

  • Higher performance and efficiency for handling large streams of data
  • More extensive set of operators and utilities for stream manipulation
  • Better support for backpressure and resource management

Cons of most

  • Steeper learning curve due to more complex API
  • Less standardized approach compared to the TC39 proposal
  • Potentially more challenging integration with existing JavaScript ecosystems

Code Comparison

proposal-observable:

const observable = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.complete();
});

observable.subscribe(value => console.log(value));

most:

import { from } from 'most';

const stream = from([1, 2]);

stream.observe(value => console.log(value));

Both examples demonstrate creating and subscribing to a simple stream of values. proposal-observable uses a more traditional Observable pattern, while most employs a functional approach with streams.

The most library offers more advanced features for complex stream operations, while proposal-observable aims to provide a standardized, basic Observable implementation for JavaScript.

proposal-observable focuses on establishing a common ground for asynchronous data streams in JavaScript, potentially leading to better ecosystem integration. most, on the other hand, prioritizes performance and advanced stream manipulation capabilities, making it more suitable for complex reactive programming scenarios.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

ECMAScript Observable

This proposal introduces an Observable type to the ECMAScript standard library. The Observable type can be used to model push-based data sources such as DOM events, timer intervals, and sockets. In addition, observables are:

  • Compositional: Observables can be composed with higher-order combinators.
  • Lazy: Observables do not start emitting data until an observer has subscribed.

Example: Observing Keyboard Events

Using the Observable constructor, we can create a function which returns an observable stream of events for an arbitrary DOM element and event type.

function listen(element, eventName) {
    return new Observable(observer => {
        // Create an event handler which sends data to the sink
        let handler = event => observer.next(event);

        // Attach the event handler
        element.addEventListener(eventName, handler, true);

        // Return a cleanup function which will cancel the event stream
        return () => {
            // Detach the event handler from the element
            element.removeEventListener(eventName, handler, true);
        };
    });
}

We can then use standard combinators to filter and map the events in the stream, just like we would with an array.

// Return an observable of special key down commands
function commandKeys(element) {
    let keyCommands = { "38": "up", "40": "down" };

    return listen(element, "keydown")
        .filter(event => event.keyCode in keyCommands)
        .map(event => keyCommands[event.keyCode])
}

Note: The "filter" and "map" methods are not included in this proposal. They may be added in a future version of this specification.

When we want to consume the event stream, we subscribe with an observer.

let subscription = commandKeys(inputElement).subscribe({
    next(val) { console.log("Received key command: " + val) },
    error(err) { console.log("Received an error: " + err) },
    complete() { console.log("Stream complete") },
});

The object returned by subscribe will allow us to cancel the subscription at any time. Upon cancelation, the Observable's cleanup function will be executed.

// After calling this function, no more events will be sent
subscription.unsubscribe();

Motivation

The Observable type represents one of the fundamental protocols for processing asynchronous streams of data. It is particularly effective at modeling streams of data which originate from the environment and are pushed into the application, such as user interface events. By offering Observable as a component of the ECMAScript standard library, we allow platforms and applications to share a common push-based stream protocol.

Implementations

Running Tests

To run the unit tests, install the es-observable-tests package into your project.

npm install es-observable-tests

Then call the exported runTests function with the constructor you want to test.

require("es-observable-tests").runTests(MyObservable);

API

Observable

An Observable represents a sequence of values which may be observed.

interface Observable {

    constructor(subscriber : SubscriberFunction);

    // Subscribes to the sequence with an observer
    subscribe(observer : Observer) : Subscription;

    // Subscribes to the sequence with callbacks
    subscribe(onNext : Function,
              onError? : Function,
              onComplete? : Function) : Subscription;

    // Returns itself
    [Symbol.observable]() : Observable;

    // Converts items to an Observable
    static of(...items) : Observable;

    // Converts an observable or iterable to an Observable
    static from(observable) : Observable;

}

interface Subscription {

    // Cancels the subscription
    unsubscribe() : void;

    // A boolean value indicating whether the subscription is closed
    get closed() : Boolean;
}

function SubscriberFunction(observer: SubscriptionObserver) : (void => void)|Subscription;

Observable.of

Observable.of creates an Observable of the values provided as arguments. The values are delivered synchronously when subscribe is called.

Observable.of("red", "green", "blue").subscribe({
    next(color) {
        console.log(color);
    }
});

/*
 > "red"
 > "green"
 > "blue"
*/

Observable.from

Observable.from converts its argument to an Observable.

  • If the argument has a Symbol.observable method, then it returns the result of invoking that method. If the resulting object is not an instance of Observable, then it is wrapped in an Observable which will delegate subscription.
  • Otherwise, the argument is assumed to be an iterable and the iteration values are delivered synchronously when subscribe is called.

Converting from an object which supports Symbol.observable to an Observable:

Observable.from({
    [Symbol.observable]() {
        return new Observable(observer => {
            setTimeout(() => {
                observer.next("hello");
                observer.next("world");
                observer.complete();
            }, 2000);
        });
    }
}).subscribe({
    next(value) {
        console.log(value);
    }
});

/*
 > "hello"
 > "world"
*/

let observable = new Observable(observer => {});
Observable.from(observable) === observable; // true

Converting from an iterable to an Observable:

Observable.from(["mercury", "venus", "earth"]).subscribe({
    next(value) {
        console.log(value);
    }
});

/*
 > "mercury"
 > "venus"
 > "earth"
*/

Observer

An Observer is used to receive data from an Observable, and is supplied as an argument to subscribe.

All methods are optional.

interface Observer {

    // Receives the subscription object when `subscribe` is called
    start(subscription : Subscription);

    // Receives the next value in the sequence
    next(value);

    // Receives the sequence error
    error(errorValue);

    // Receives a completion notification
    complete();
}

SubscriptionObserver

A SubscriptionObserver is a normalized Observer which wraps the observer object supplied to subscribe.

interface SubscriptionObserver {

    // Sends the next value in the sequence
    next(value);

    // Sends the sequence error
    error(errorValue);

    // Sends the completion notification
    complete();

    // A boolean value indicating whether the subscription is closed
    get closed() : Boolean;
}