Skip to content

Commit 5fe4d4c

Browse files
authored
Merge pull request #5 from canjs/major
DON'T MERGE: Updated to be prototype based, use queues and ObservationRecorder
2 parents 3d03fd2 + 37323b8 commit 5fe4d4c

File tree

17 files changed

+1548
-73
lines changed

17 files changed

+1548
-73
lines changed

async/async-test.js

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
var QUnit = require('steal-qunit');
2+
var AsyncObservable = require('./async');
3+
var SimpleObservable = require('../can-simple-observable');
4+
var canReflect = require('can-reflect');
5+
var ObservationRecorder = require("can-observation-recorder");
6+
var Observation = require("can-observation");
7+
8+
QUnit.module('can-simple-observable/async');
9+
10+
QUnit.test('basics', function(assert){
11+
var done = assert.async();
12+
var value = new SimpleObservable(1);
13+
14+
15+
var obs = new AsyncObservable(function(lastSet, resolve){
16+
if(!resolve) {
17+
return "default";
18+
}
19+
if(value.get() === 1) {
20+
setTimeout(function(){
21+
resolve("a");
22+
}, 1);
23+
} else {
24+
setTimeout(function(){
25+
resolve("b");
26+
}, 1);
27+
}
28+
});
29+
30+
// Unbound and unobserved behavior
31+
QUnit.equal(canReflect.getValue(obs), 'default', 'getValue unbound');
32+
33+
// Unbound , being observed behavior
34+
ObservationRecorder.start();
35+
QUnit.equal(canReflect.getValue(obs), undefined, "getValue being bound");
36+
var dependencies = ObservationRecorder.stop();
37+
QUnit.ok(!dependencies.valueDependencies.has(value), "did not record value");
38+
QUnit.ok(dependencies.valueDependencies.has(obs), "did record observable");
39+
QUnit.equal(dependencies.valueDependencies.size, 1, "only one value to listen to");
40+
41+
var changes = 0;
42+
var handler = function(newValue) {
43+
changes++;
44+
if(changes === 1) {
45+
QUnit.equal(newValue, 'a', 'onValue a');
46+
value.set(2);
47+
} else {
48+
QUnit.equal(newValue, 'b', 'onValue b');
49+
done();
50+
}
51+
};
52+
canReflect.onValue(obs, handler);
53+
54+
});
55+
56+
57+
QUnit.test("get and set Priority", function(){
58+
var value = new SimpleObservable(1);
59+
60+
var obs = new AsyncObservable(function(lastSet, resolve){
61+
if(!resolve) {
62+
return "default";
63+
}
64+
if(value.get() === 1) {
65+
setTimeout(function(){
66+
resolve("a");
67+
}, 1);
68+
} else {
69+
setTimeout(function(){
70+
resolve("b");
71+
}, 1);
72+
}
73+
});
74+
75+
canReflect.setPriority(obs, 5);
76+
77+
QUnit.equal(canReflect.getPriority(obs), 5, "set priority");
78+
});
79+
80+
QUnit.test("prevent a getter returning undefined from overwriting last resolved value", function(){
81+
var value = new SimpleObservable(1);
82+
83+
var obs = new AsyncObservable(function(lastSet, resolve){
84+
if(value.get() === 1) {
85+
return null;
86+
} else {
87+
resolve(4);
88+
}
89+
90+
});
91+
obs.on(function(){});
92+
QUnit.equal( obs.get(), null );
93+
value.set(2);
94+
95+
QUnit.equal( obs.get(), 4 );
96+
97+
});
98+
99+
QUnit.test("prevent a getter returning undefined from overwriting last resolved value at the start", function(){
100+
var value = new SimpleObservable(1);
101+
102+
var obs = new AsyncObservable(function(lastSet, resolve){
103+
resolve(value.get()*2);
104+
});
105+
obs.on(function(){});
106+
QUnit.equal( obs.get(), 2 );
107+
value.set(2);
108+
109+
QUnit.equal( obs.get(), 4 );
110+
111+
});
112+
113+
if(System.env.indexOf("production") < 0) {
114+
QUnit.test("log async observable changes", function(assert) {
115+
var done = assert.async();
116+
var value = new SimpleObservable(1);
117+
118+
var obs = new AsyncObservable(function(lastSet, resolve) {
119+
if (value.get() === 1) {
120+
setTimeout(function(){
121+
resolve("b");
122+
}, 1);
123+
} else {
124+
setTimeout(function(){
125+
resolve("c");
126+
}, 1);
127+
}
128+
}, null, "a");
129+
130+
// turn on logging
131+
obs.log();
132+
133+
// override the internal _log to spy on arguments
134+
var changes = [];
135+
obs._log = function(previous, current) {
136+
changes.push({ previous: previous, current: current });
137+
};
138+
139+
// make sure the observable is bound
140+
canReflect.onValue(obs, function() {});
141+
142+
// trigger observation changes
143+
value.set("2");
144+
145+
assert.expect(1);
146+
setTimeout(function() {
147+
assert.deepEqual(changes, [
148+
{ current: "b", previous: undefined },
149+
{ current: "c", previous: "b" },
150+
]);
151+
done();
152+
}, 10);
153+
});
154+
}
155+
156+
QUnit.test("getValueDependencies", function(assert) {
157+
var value = new SimpleObservable(1);
158+
159+
var obs = new AsyncObservable(function(lastSet, resolve){
160+
return value.get() === 1 ? lastSet : resolve(4);
161+
});
162+
163+
// unbound
164+
assert.equal(
165+
typeof canReflect.getValueDependencies(obs),
166+
"undefined",
167+
"should be undefined when observable is unbound"
168+
);
169+
170+
// bound
171+
canReflect.onValue(obs, function() {});
172+
assert.deepEqual(
173+
canReflect.getValueDependencies(obs).valueDependencies,
174+
new Set([obs.lastSetValue, value])
175+
);
176+
});
177+
178+
QUnit.test("if a value is resolved and another is returned - returned value should be used (#13)", function(assert) {
179+
var changeCount = 0;
180+
var value = new SimpleObservable(1);
181+
182+
var asyncObs = new AsyncObservable(function(lastSet, resolve){
183+
resolve("resolved value");
184+
return "returned value";
185+
});
186+
187+
canReflect.onValue(asyncObs, function() {
188+
changeCount++;
189+
});
190+
191+
assert.equal( asyncObs.get(), "returned value", "returned value");
192+
assert.equal(changeCount, 1, "only one change event occured");
193+
194+
});
195+
196+
QUnit.test("return, resolve, and then return again should work and should update dependent observations (#13)", function(assert) {
197+
var done = assert.async();
198+
var value = new SimpleObservable(null);
199+
200+
var p1 = new Promise(function(resolve) {
201+
resolve("resolved value 1");
202+
});
203+
204+
var p2 = new Promise(function(resolve) {
205+
setTimeout(function() {
206+
resolve("resolved value 2");
207+
}, 10);
208+
});
209+
210+
var asyncObs = new AsyncObservable(function(lastSet, resolve){
211+
var thePromise = value.get();
212+
if (thePromise) {
213+
thePromise.then(function(data) {
214+
resolve(data);
215+
});
216+
}
217+
return "returned value";
218+
});
219+
220+
var wrapper = new Observation(function() {
221+
return asyncObs.get();
222+
});
223+
canReflect.onValue(wrapper, function() {});
224+
225+
assert.equal(asyncObs.get(), "returned value", "returned value");
226+
assert.equal(canReflect.getValue(wrapper), "returned value", "observation - returned value");
227+
228+
value.set(p1);
229+
setTimeout(function() {
230+
assert.equal(asyncObs.get(), "resolved value 1", "resolved value 1");
231+
assert.equal(canReflect.getValue(wrapper), "resolved value 1", "observation - resolved value 1");
232+
233+
value.set(p2);
234+
assert.equal(asyncObs.get(), "returned value", "returned value");
235+
assert.equal(canReflect.getValue(wrapper), "returned value", "observation - returned value");
236+
237+
setTimeout(function() {
238+
assert.equal(asyncObs.get(), "resolved value 2", "resolved value 2");
239+
assert.equal(canReflect.getValue(wrapper), "resolved value 2", "observation - resolved value 2");
240+
241+
done();
242+
}, 10);
243+
});
244+
});
245+
246+
QUnit.test("resolving, then later returning should not cause duplicate events (#13)", function(assert) {
247+
var count = 0;
248+
var id = new SimpleObservable(null);
249+
250+
var asyncObs = new AsyncObservable(function(lastSet, resolve) {
251+
if (lastSet) {
252+
return lastSet
253+
} else if (id.get()) {
254+
resolve("resolved value");
255+
}
256+
});
257+
258+
canReflect.onValue(asyncObs, function(newVal) {
259+
count++;
260+
});
261+
262+
id.set("trigger a change");
263+
assert.equal(asyncObs.get(), "resolved value", "resolved value");
264+
265+
asyncObs.set("set value");
266+
assert.equal(asyncObs.get(), "set value", "set value");
267+
268+
assert.equal(count, 2, "2 change events");
269+
});

async/async.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
var SimpleObservable = require("../can-simple-observable");
2+
var Observation = require("can-observation");
3+
var queues = require("can-queues");
4+
var SettableObservable = require("../settable/settable");
5+
var canReflect = require("can-reflect");
6+
var ObservationRecorder = require("can-observation-recorder");
7+
var valueEventBindings = require("can-event-queue/value/value");
8+
9+
// This is an observable that is like `settable`, but passed a `resolve`
10+
// function that can resolve the value of this observable late.
11+
function AsyncObservable(fn, context, initialValue) {
12+
this.resolve = this.resolve.bind(this);
13+
this.lastSetValue = new SimpleObservable(initialValue);
14+
this.handler = this.handler.bind(this);
15+
16+
function observe() {
17+
this.resolveCalled = false;
18+
19+
// set inGetter flag to avoid calling `resolve` redundantly if it is called
20+
// synchronously in the getter
21+
this.inGetter = true;
22+
var newVal = fn.call(
23+
context,
24+
this.lastSetValue.get(),
25+
this.bound === true ? this.resolve : undefined
26+
);
27+
this.inGetter = false;
28+
29+
// if the getter returned a value, resolve with the value
30+
if (newVal !== undefined) {
31+
this.resolve(newVal);
32+
}
33+
// otherwise, if `resolve` was called synchronously in the getter,
34+
// resolve with the value passed to `resolve`
35+
else if (this.resolveCalled) {
36+
this.resolve(this.value);
37+
}
38+
39+
// if bound, the handlers will be called by `resolve`
40+
// returning here would cause a duplicate event
41+
if (this.bound !== true) {
42+
return newVal;
43+
}
44+
}
45+
46+
//!steal-remove-start
47+
canReflect.assignSymbols(this, {
48+
"can.getName": function() {
49+
return (
50+
canReflect.getName(this.constructor) +
51+
"<" +
52+
canReflect.getName(fn) +
53+
">"
54+
);
55+
}
56+
});
57+
Object.defineProperty(this.handler, "name", {
58+
value: canReflect.getName(this) + ".handler"
59+
});
60+
Object.defineProperty(observe, "name", {
61+
value: canReflect.getName(fn) + "::" + canReflect.getName(this.constructor)
62+
});
63+
//!steal-remove-end
64+
65+
this.observation = new Observation(observe, this);
66+
}
67+
AsyncObservable.prototype = Object.create(SettableObservable.prototype);
68+
AsyncObservable.prototype.constructor = AsyncObservable;
69+
70+
AsyncObservable.prototype.handler = function(newVal) {
71+
if (newVal !== undefined) {
72+
SettableObservable.prototype.handler.apply(this, arguments);
73+
}
74+
};
75+
76+
var peek = ObservationRecorder.ignore(canReflect.getValue.bind(canReflect));
77+
AsyncObservable.prototype.onBound = function() {
78+
this.bound = true;
79+
canReflect.onValue(this.observation, this.handler, "notify");
80+
if (!this.resolveCalled) {
81+
this.value = peek(this.observation);
82+
}
83+
};
84+
85+
AsyncObservable.prototype.resolve = function resolve(newVal) {
86+
this.resolveCalled = true;
87+
var old = this.value;
88+
this.value = newVal;
89+
90+
//!steal-remove-start
91+
if (typeof this._log === "function") {
92+
this._log(old, newVal);
93+
}
94+
//!steal-remove-end
95+
96+
// if resolve was called synchronously from the getter, do not enqueue changes
97+
// the observation will handle calling resolve again if required
98+
if (!this.inGetter) {
99+
// adds callback handlers to be called w/i their respective queue.
100+
queues.enqueueByQueue(
101+
this.handlers.getNode([]),
102+
this,
103+
[newVal, old],
104+
null
105+
//!steal-remove-start
106+
/* jshint laxcomma: true */
107+
, [canReflect.getName(this), "resolved with", newVal]
108+
/* jshint laxcomma: false */
109+
//!steal-remove-end
110+
);
111+
}
112+
};
113+
114+
module.exports = AsyncObservable;

0 commit comments

Comments
 (0)