Skip to content

Commit fdd427e

Browse files
authored
Merge pull request #494 from emberjs/async-observers
Async Observers
2 parents f34e240 + 7b4d418 commit fdd427e

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

text/0494-async-observers.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
- Start Date: 2019-05-30
2+
- Relevant Team(s): Ember.js, Learning
3+
- RFC PR: https://github.com/emberjs/rfcs/pull/494
4+
- Tracking: (leave this empty)
5+
6+
# Async Observers
7+
8+
## Summary
9+
10+
Add a way to specify whether or not observers should fire synchronously -
11+
that is, immediately after the property they are observing has changed - or
12+
asynchronously during the next runloop, along with an optional feature to
13+
specify whether observers should default to sync or async.
14+
15+
## Motivation
16+
17+
Observers have been run synchronously in Ember since before v1.0 was released,
18+
and for about as long it has been an intention of the core team to eventually
19+
make them asynchronous. There are a two main reasons for why triggering
20+
observers asynchronously would be better overall:
21+
22+
- They promote better programming practices. Synchronous observers can be used
23+
in a lot of ways to interact with the code they are observing, which puts more
24+
code on the "hot-path" and is prone to create a mess of intertangled, loosely
25+
related code filled with [spooky action at a distance](<https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)>).
26+
- It would allow us to clean up a significant chunk of code within Ember
27+
itself. There is non-trivial amount of code dedicated to sending change
28+
signals synchronously, and that code has been slowly replaced by an
29+
alternative system that is lazy. Asynchronous observers would allow us to
30+
remove legacy code and tech debt.
31+
32+
We implemented this change behind a feature flag, and several community members
33+
tested it out in their applications. In testing, we found that this was
34+
unfortunately too much of a breaking change to do all at once - like it or not,
35+
the timing semantics of observers are public API.
36+
37+
The proposed solution now is to provide a method for users to specify whether an
38+
observer should be sync or async. In existing apps, observers can be converted
39+
incrementally to be async, giving them a path forward. In addition, an optional
40+
feature will be made which sets observers to be async by default, allowing users
41+
to set the default once their whole app has been converted, and allowing new
42+
apps to prevent/discourage sync observers in the first place. In the long run,
43+
synchronous observers will be deprecated and removed.
44+
45+
## Detailed design
46+
47+
### New APIs
48+
49+
A new `sync` boolean argument will be added to both `addObserver` and
50+
`removeObserver`:
51+
52+
```ts
53+
export function addObserver(
54+
obj: any,
55+
path: string,
56+
target: object | Function | null,
57+
method?: string | Function,
58+
sync = SYNC_DEFAULT
59+
): void;
60+
61+
export function removeObserver(
62+
obj: any,
63+
path: string,
64+
target: object | Function | null,
65+
method?: string | Function,
66+
sync = SYNC_DEFAULT
67+
): void;
68+
```
69+
70+
The argument needs to be added to both because sync and async observers are
71+
tracked separately, so we need to know where to look for the observer when
72+
removing it. Attempting to add both a sync and async observer will throw an
73+
error.
74+
75+
In addition, a new overloaded form of `observer` will allow users to specify
76+
whether or not the observer should be sync or async:
77+
78+
```ts
79+
type ObserverDefinition = {
80+
dependentKeys: string[];
81+
fn: Function;
82+
sync: boolean;
83+
};
84+
85+
export function observer(...args: (string | Function)[]): Function;
86+
export function observer(definition: ObserverDefinition): Function;
87+
```
88+
89+
Users will have to provide a full `ObserverDefinition` to set `sync`, which will
90+
prevent us from having to do any more argument munging to figure out what the
91+
user wants.
92+
93+
### Synchronous Observer Implementation
94+
95+
Since chains are removed, the only way to check if observers should fire is to
96+
cycle through all of them. This means that on every `notifyPropertyChange`, we
97+
will cycle through _all_ active synchronous observers and fire any that have
98+
dirtied.
99+
100+
In apps that are observer heavy, this could lead to performance impacts.
101+
Unfortunately, there isn't much we can do about this. We will try to minimize
102+
the impact as much as possible, but in the end it will be up to individual
103+
applications to migrate away from synchronous observers over time.
104+
105+
### Tracked Properties and `@dependentKeyCompat`
106+
107+
Tracked properties and `@dependentKeyCompat` marked getters/setters will _not_
108+
fire observers synchronously, since they do not use `notifyPropertyChange` or
109+
the old change tracking system at all. In this way, they will encourage users to
110+
convert to async observers, or away from observers entirely.
111+
112+
### Optional Feature
113+
114+
The name of the feature will be `default-async-observers`. Enabling it will
115+
default all observers to be async, but still allow users to set observers to be
116+
synchronous manually. This flag will be enabled by default in Ember Octane.
117+
118+
## How we teach this
119+
120+
### API Docs
121+
122+
(To be added at the [end of the current API docs](https://github.com/emberjs/ember.js/blob/4a98e1610b795edb544513f10a8870af1375141d/packages/%40ember/-internals/runtime/lib/mixins/observable.js#L359))
123+
124+
#### `sync`
125+
126+
By default in new Ember applications, observers are asynchronous. They can be
127+
marked as _synchronous_ instead by using the `sync` option. Synchronous
128+
observers will run immediately when the property they are observing changes,
129+
instead of being scheduled to run later.
130+
131+
Each synchronous observer has a performance impact for every property change, so
132+
you should generally avoid using synchronous observers.
133+
134+
In older applications, observers are synchronous by default. You can use the
135+
`sync` option to make them asynchronous instead and convert them over time. You
136+
can also enable the `default-async-observers` optional feature to make them
137+
asynchronous by default, once you are sure that they will continue to function
138+
if they are asynchronous.
139+
140+
### Guides
141+
142+
Observers are not discussed in the post-Octane guides, since we don't want to
143+
encourage their use. It may make sense to include a section on them in the
144+
upgrade guide instead.
145+
146+
### Upgrade Guides
147+
148+
We should make a note in the Octane upgrade guides that sync observers are
149+
discouraged and probably not very performant. We should be up front that this
150+
will likely be a performance hit, but that we decided it was worth the benefits
151+
of the change.
152+
153+
## Drawbacks
154+
155+
The biggest potential drawback is in performance. While we haven't been able to
156+
do any testing on apps that have observers, its possible that these changes will
157+
have an impact on them, especially apps that have many observers.
158+
159+
In theory, this shouldn't impact the majority of Ember apps since observers have
160+
been discouraged so heavily for such a long time. The impact should also
161+
decrease in time, as users transition away from observers entirely and toward
162+
tracked properties.
163+
164+
## Alternatives
165+
166+
- We could release Ember v4, and ship asynchronous observers as a breaking
167+
change. We currently believe this would be a breaking change that would
168+
prevent many users from adopting Octane or transitioning forward to tracked
169+
properties, which would be problematic and could divide the community.
170+
171+
## Unresolved questions
172+
173+
What is the exact performance impact? Can we test it out in an application that
174+
represents a typical Ember app that uses observers?

0 commit comments

Comments
 (0)