Skip to content
This repository has been archived by the owner on Mar 3, 2023. It is now read-only.

Clean Up Workspace Event Subscription API #3418

Merged
merged 65 commits into from
Sep 4, 2014
Merged

Conversation

nathansobo
Copy link
Contributor

In #3378 I discussed the idea of integrating RxJS observables for our event system. But in the interest of getting to the API freeze faster, I think we should avoid pulling a complex framework into core at this time. This PR focuses on simplifying and clarifying the events API without introducing observables, but the proposed changes are compatible with adding RxJS observables in the future.

The goal of this PR is to reform all the event APIs to use methods instead of strings for all the objects involved in everything in workspace.

Tasks

  • Add New APIs
    • Workspace
      • Pane
      • PaneAxis
      • PaneContainer
  • Document New APIs
  • Deprecate Old APIs
    • Deprecate theorist-dependent code paths
    • Deprecate direct calls to ::on
    • Deprecate supplanted methods

API Conventions

  • All event subscriptions should occur through explicit methods rather than through strings passed to ::on.
  • Event subscription methods take callbacks as their final argument. In the future, we can add the ability to return an observable when no callback is provided.
  • Event subscription methods return disposables. When you call .dispose() on the returned disposable the subscription is cancelled.
  • Event callbacks are always invoked with a single object. If multiple pieces of information need to be passed, the object can be a hash with named entries.
  • Event subscription methods conform to the following naming conventions:
    • Discrete events are named with onDid[Verb][Noun] or onWill[Verb][Noun]:
      • onDidActivate
      • onDidAddItem
      • onWillDestroyItem
    • Properties that change can have two kinds of methods:
      • Methods that invoke the callback the next time the property changes take the form onDidChange[PropertyName]:
        • onDidChangeActiveItem
      • Methods that invoke the callback immediately with the current value and also whenever it changes take the form observe[PropertyName]:
        • observeActiveItem
    • Collections can be observed with observe[CollectionName], which will invoke the callback with all current and future elements:
      • observePanes

Usage Guidelines

I've created a simple npm called event-kit. The goal is to provide a few building blocks for implementing and consuming evented APIs while maintaining maximal simplicity.

Implementing Evented APIs

We want to move away from mixins and toward composition. Toward that end, event-kit exports a simple Emitter object that can be used to streamline evented APIs. Rather than mixing it in, you construct an Emitter instance and use it internally. Here's a sample of how you use it:

{Emitter} = require 'event-kit'

class Pane
  constructor: ->
    # implementation...
    @emitter = new Emitter

  onDidChangeActiveItem: (fn) ->
    @emitter.on 'did-change-active-item', fn

  setActiveItem: (activeItem) ->
    unless activeItem is @activeItem
      @activeItem = activeItem
      @emitter.emit 'did-change-active-item', activeItem
    @activeItem

This is similar to the existing emitter from emissary, but it's maximally simple and omits several features such as namespaces and support for multiple event arguments. The ::on method also returns Disposable instances which unsubscribe when .dispose() is called on them.

Using emitters via instances instead of mixins ensures users won't call ::on directly and gives us leverage for deprecating existing direct usages of ::on.

If needed, Disposable can be used directly. You build a disposable with a function that is called when the disposable is disposed. This intentionally follows the same API as RxJS.

{Disposable} = require 'event-kit'

disposable = new Disposable(=> @cleanUp())

Consuming Evented APIs

We want to encourage users to dispose of subscriptions when they are done with them, and we now think the best way to do this is to make it explicit. So now rather than using the Subscriber mixin, we will encourage users to build their own CompositeDisposable instance and add subscriptions to it.

{CompositeDisposable} = require 'event-kit'

class TabBar
  constructor: (@pane) ->
    @subscriptions = new CompositeDisposable
    @subscriptions.add @pane.observeActiveItem -> # ...
    @subscriptions.add @pane.onDidAddItem -> # ...
    @subscriptions.add @pane.onDidRemoveItem -> # ...

  destroyed: ->
    @subscriptions.dispose()

Users will need to remember to dispose of the subscriptions, but we think that making it more explicit will help clarify what is going on in examples and increase the likelihood that package authors clean up.

Nathan Sobo added 22 commits August 28, 2014 11:42
This branch uses EventKit, an ultra-simple library for implementing
events. The object implementing the methods maintains its own emitter
object rather than doing a mixin like Emissary encourages. This will
make it easier for us to deprecate ::on on the object itself. Unlike
emissary, the EventKit Emitter implements a super minimalistic API that
only allows one value to be emitted and always returns a Disposable
from subscriptions.
These have behavior semantics, invoking the observer immediately with
the current value of the observed property.
This eliminates our reliance on the Sequence object for informing us
of changes
Now that emissary’s Subscriber no longer conflicts with the name
@nathansobo nathansobo changed the title Clean Up Workspace Event APIs Clean Up Workspace Events API Aug 28, 2014
@benogle
Copy link
Contributor

benogle commented Aug 28, 2014

I like this a lot a lot. ❤️ Excited about event kit. I was seeing emissary as a major source of garbage while profiling fnr.

Event Deprecations

There are a few events on Pane and Workspace. What's the strategy for deprecation?

How are we going to deprecate them? We can provide an override ::on method, check for the strings, and call the relevant new function?

API Conventions

A note for later: the API conventions you've written should go in the api-guidelines doc when this is merged. Really good things!


# Public: Invoke the given callback with all current and future items.
#
# * `callback` {Function} to be called with current and future items.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to do the param(s) to this callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

@nathansobo nathansobo changed the title Clean Up Workspace Events API Clean Up Event Subscription API Sep 3, 2014
nathansobo pushed a commit to atom/text-buffer that referenced this pull request Sep 3, 2014
This allows this package to be upgraded on master if needed before we
merge atom/atom#3418.
@nathansobo nathansobo changed the title Clean Up Event Subscription API Clean Up Workspace Event Subscription API Sep 3, 2014
nathansobo pushed a commit that referenced this pull request Sep 4, 2014
Clean Up Workspace Event Subscription API
@nathansobo nathansobo merged commit 621c247 into master Sep 4, 2014
@nathansobo nathansobo deleted the ns-simplify-events branch September 4, 2014 13:47
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants