Skip to content

Commit

Permalink
updates based on feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Garrett committed Feb 28, 2020
1 parent 4a1d22c commit 35b84e2
Showing 1 changed file with 93 additions and 13 deletions.
106 changes: 93 additions & 13 deletions text/0580-destroyables.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class TimeoutManager {
constructor(parent, fn, timeout = 1000) {
let timeoutId = setTimeout(fn, timeout);

registerLifetime(parent, this);
associateDestroyableChild(parent, this);
registerDestructor(this, () => clearTimeout(timeoutId))
}
}
Expand All @@ -44,7 +44,8 @@ Ember manages the lifecycles and lifetimes of many built in constructs, such as
components, and does so in a hierarchical way - when a parent component is
destroyed, all of its children are destroyed as well. This is a well established
software pattern that is useful for many applications, and there are a variety
of libraries, such as [ember-lifeline](https://github.com/ember-lifeline/ember-lifeline) and [ember-concurrency](https://github.com/machty/ember-concurrency), that would
of libraries, such as [ember-lifeline](https://github.com/ember-lifeline/ember-lifeline)
and [ember-concurrency](https://github.com/machty/ember-concurrency), that would
benefit from having a way to extend this hierarchy, adding their own "children"
that are cleaned up whenever their parents are removed.

Expand Down Expand Up @@ -95,39 +96,118 @@ maximizes interoperability in general.

## Detailed design

The API consists of 3 functions:
The API consists of 6 main functions, imported from `@ember/application`:

```ts
declare function registerLifetime(parent: object, child: object): void;
declare function associateDestroyableChild(parent: object, child: object): void;
declare function registerDestructor(destroyable: object, destructor: () => void): void;
declare function unregisterDestructer(destroyable: object, destructor: () => void): void;

declare function destroy(destroyable: object): void;
declare function isDestroying(destroyable: object): boolean;
declare function isDestroyed(destroyable: object): boolean;
```

In addition, there is a debug-only mode function used for testing:

```ts
declare function assertDestroyablesDestroyed(): void;
```

### `registerLifetime`
#### `associateDestroyableChild`

This function is used to associate a destroyable object with a parent. When the
parent is destroyed, all registered children will also be destroyed.

This function is used to associate a destroyable object's lifetime with a
parent. When the parent is destroyed, all registered children will also be
destroyed.
- Attempting to associate a parent or child that has already been destroyed will
throw an error.
- Attempting to associate a child with a parent that is not a destroyable should
throw an error.

### `registerDestructor`
##### Multiple Inheritance

Attempting to associate a child to multiple parents will currently throw an
error. This could be changed in the future, but for the time being multiple
inheritance of destructors is tricky and not scoped in. Instead, users can add
destructors to accomplish this goal:

```js
let parent1 = {}, parent2 = {}, child = {};

registerDestructor(parent1, () => destroy(child));
registerDestructor(parent2, () => destroy(child));
```

The exact timing semantics here will be a bit different, but for most use cases
this should be fine. If we find that it would be useful to have multiple
inheritance baked in in the future, it can be added in a followup RFC.

#### `registerDestructor`

Receives a destroyable and a destructor function, and associates the function
with it. When the destroyable is destroyed, or when its parent is destroyed, the
destructor function will be called. Multiple destructors can be associated with
a given destroyable, and they can be associated over time, allowing libraries
like `ember-lifeline` to dynamically add destructors as needed.

### `destroy`
- Registering a destructor on a destroyed object should throw an error.
- Attempting to register the same destructor multiple times should throw an
error.
- Attempting to register a destructor on a non-destroyable should throw an
error.

#### `unregisterDestructor`

Receives a destroyable and a destructor function, and de-associates the
destructor from the destroyable.

- Calling `unregisterDestructor` on a destroyed object should throw an error.
- Calling `unregisterDestructor` with a destructor that is not associated with
the object should throw an error.
- Calling `unregisterDestructor` on a non-destroyable should throw an error.

#### `destroy`

`destroy` manually initiates the destruction of a destroyable. This allows users
to manually manage the lifecycles of destroyables that are shorter lived than
their parents (for instance, destroyables that are defined an a service, which
lives as long as an application does), cleaning them up as needed.

Calling `destroy` multiple times on the same object is safe. It will not throw
an error, and will not take any further action.

- Calling `destroy` on a non-destroyable should throw an error.

#### `isDestroying`

This would return true as soon as the destroyable begins destruction, before any
of its destructors are called. Returns false otherwise.

- Calling `isDestroying` on a non-destroyable should throw an error.

#### `isDestroyed`

This would return true as soon after the destroyable has been fully destroyed,
including all of its child destroyables. Returns false otherwise.

- Calling `isDestroyed` on a non-destroyable should throw an error.

#### `assertDestroyablesDestroyed`

This function asserts that all active destroyables have been destroyed at the
time it is called. It is meant to be a low level hook that testing frameworks
like `ember-qunit` and `ember-mocha` can use to hook into and validate that all
destroyables have in fact been destroyed.

### Timing Semantics

Destroyables first call their own destructors, then destroy their associated
children.

### Built In Destroyables

The root destroyable of an Ember application will be the instance of the
application itself. All framework managed classes are destroyables, including:
The root destroyable of an Ember application will be the instance of the owner.
All framework managed classes are destroyables, including:

* Components
* Services
Expand All @@ -143,7 +223,7 @@ also be marked as destroyables.

This is a generally low level API, meant for usage in the framework and in
addons. It should be taught primarily through API documentation, and potentially
through and in-depth guide.
through an in-depth guide.

## Drawbacks

Expand Down

0 comments on commit 35b84e2

Please sign in to comment.