Skip to content

Commit f7ac080

Browse files
committed
Merge pull request #1406 from tchak/deferred-then
Add Ember.Deferred mixin which implements Promises/A spec
2 parents 0184c2c + 2f8d5e6 commit f7ac080

File tree

3 files changed

+382
-0
lines changed

3 files changed

+382
-0
lines changed

‎packages/ember-runtime/lib/mixins.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ require('ember-runtime/mixins/mutable_enumerable');
88
require('ember-runtime/mixins/observable');
99
require('ember-runtime/mixins/target_action_support');
1010
require('ember-runtime/mixins/evented');
11+
require('ember-runtime/mixins/deferred');
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
var get = Ember.get, set = Ember.set,
2+
slice = Array.prototype.slice,
3+
forEach = Ember.ArrayPolyfills.forEach;
4+
5+
var Callbacks = function(target, once) {
6+
this.target = target;
7+
this.once = once || false;
8+
this.list = [];
9+
this.fired = false;
10+
this.off = false;
11+
};
12+
13+
Callbacks.prototype = {
14+
add: function(callback) {
15+
if (this.off) { return; }
16+
17+
this.list.push(callback);
18+
19+
if (this.fired) { this.flush(); }
20+
},
21+
22+
fire: function() {
23+
if (this.off || this.once && this.fired) { return; }
24+
if (!this.fired) { this.fired = true; }
25+
26+
this.args = slice.call(arguments);
27+
28+
if (this.list.length > 0) { this.flush(); }
29+
},
30+
31+
flush: function() {
32+
Ember.run.once(this, 'flushCallbacks');
33+
},
34+
35+
flushCallbacks: function() {
36+
forEach.call(this.list, function(callback) {
37+
callback.apply(this.target, this.args);
38+
}, this);
39+
if (this.once) { this.list = []; }
40+
}
41+
};
42+
43+
44+
/**
45+
@class
46+
47+
@extends Ember.Mixin
48+
*/
49+
Ember.Deferred = Ember.Mixin.create(
50+
/** @scope Ember.Deferred.prototype */ {
51+
52+
/**
53+
Add handlers to be called when the Deferred object is resolved or rejected.
54+
*/
55+
then: function(doneCallback, failCallback, progressCallback) {
56+
if (doneCallback) {
57+
get(this, 'deferredDone').add(doneCallback);
58+
}
59+
if (failCallback) {
60+
get(this, 'deferredFail').add(failCallback);
61+
}
62+
if (progressCallback) {
63+
get(this, 'deferredProgress').add(progressCallback);
64+
}
65+
66+
return this;
67+
},
68+
69+
/**
70+
Call the progressCallbacks on a Deferred object with the given args.
71+
*/
72+
notify: function() {
73+
var callbacks = get(this, 'deferredProgress');
74+
callbacks.fire.apply(callbacks, slice.call(arguments));
75+
76+
return this;
77+
},
78+
79+
/**
80+
Resolve a Deferred object and call any doneCallbacks with the given args.
81+
*/
82+
resolve: function() {
83+
var callbacks = get(this, 'deferredDone');
84+
callbacks.fire.apply(callbacks, slice.call(arguments));
85+
set(this, 'deferredProgress.off', true);
86+
set(this, 'deferredFail.off', true);
87+
88+
return this;
89+
},
90+
91+
/**
92+
Reject a Deferred object and call any failCallbacks with the given args.
93+
*/
94+
reject: function() {
95+
var callbacks = get(this, 'deferredFail');
96+
callbacks.fire.apply(callbacks, slice.call(arguments));
97+
set(this, 'deferredProgress.off', true);
98+
set(this, 'deferredDone.off', true);
99+
100+
return this;
101+
},
102+
103+
deferredDone: Ember.computed(function() {
104+
return new Callbacks(this, true);
105+
}).cacheable(),
106+
107+
deferredFail: Ember.computed(function() {
108+
return new Callbacks(this, true);
109+
}).cacheable(),
110+
111+
deferredProgress: Ember.computed(function() {
112+
return new Callbacks(this);
113+
}).cacheable()
114+
});
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
module("Ember.Deferred");
2+
3+
test("can resolve deferred", function() {
4+
5+
var deferred, count = 0;
6+
7+
Ember.run(function() {
8+
deferred = Ember.Object.create(Ember.Deferred);
9+
});
10+
11+
deferred.then(function() {
12+
count++;
13+
});
14+
15+
stop();
16+
Ember.run(function() {
17+
deferred.resolve();
18+
});
19+
20+
setTimeout(function() {
21+
start();
22+
equal(count, 1, "done callback was called");
23+
}, 20);
24+
});
25+
26+
test("can reject deferred", function() {
27+
28+
var deferred, count = 0;
29+
30+
Ember.run(function() {
31+
deferred = Ember.Object.create(Ember.Deferred);
32+
});
33+
34+
deferred.then(function() {}, function() {
35+
count++;
36+
});
37+
38+
stop();
39+
Ember.run(function() {
40+
deferred.reject();
41+
});
42+
43+
setTimeout(function() {
44+
start();
45+
equal(count, 1, "fail callback was called");
46+
}, 20);
47+
});
48+
49+
test("can resolve with then", function() {
50+
51+
var deferred, count1 = 0 ,count2 = 0;
52+
53+
Ember.run(function() {
54+
deferred = Ember.Object.create(Ember.Deferred);
55+
});
56+
57+
deferred.then(function() {
58+
count1++;
59+
}, function() {
60+
count2++;
61+
});
62+
63+
stop();
64+
Ember.run(function() {
65+
deferred.resolve();
66+
});
67+
68+
setTimeout(function() {
69+
start();
70+
equal(count1, 1, "then were resolved");
71+
equal(count2, 0, "then was not rejected");
72+
}, 20);
73+
});
74+
75+
test("can reject with then", function() {
76+
77+
var deferred, count1 = 0 ,count2 = 0;
78+
79+
Ember.run(function() {
80+
deferred = Ember.Object.create(Ember.Deferred);
81+
});
82+
83+
deferred.then(function() {
84+
count1++;
85+
}, function() {
86+
count2++;
87+
});
88+
89+
stop();
90+
Ember.run(function() {
91+
deferred.reject();
92+
});
93+
94+
setTimeout(function() {
95+
start();
96+
equal(count1, 0, "then was not resolved");
97+
equal(count2, 1, "then were rejected");
98+
}, 20);
99+
});
100+
101+
test("can call resolve multiple times", function() {
102+
103+
var deferred, count = 0;
104+
105+
Ember.run(function() {
106+
deferred = Ember.Object.create(Ember.Deferred);
107+
});
108+
109+
deferred.then(function() {
110+
count++;
111+
});
112+
113+
stop();
114+
Ember.run(function() {
115+
deferred.resolve();
116+
deferred.resolve();
117+
deferred.resolve();
118+
});
119+
120+
setTimeout(function() {
121+
start();
122+
equal(count, 1, "calling resolve multiple times has no effect");
123+
}, 20);
124+
});
125+
126+
test("deferred has progress", function() {
127+
128+
var deferred, count = 0;
129+
130+
Ember.run(function() {
131+
deferred = Ember.Object.create(Ember.Deferred);
132+
});
133+
134+
deferred.then(function() {}, function() {}, function() {
135+
count++;
136+
});
137+
138+
stop();
139+
Ember.run(function() {
140+
deferred.notify();
141+
deferred.notify();
142+
deferred.notify();
143+
});
144+
Ember.run(function() {
145+
deferred.notify();
146+
});
147+
Ember.run(function() {
148+
deferred.notify();
149+
deferred.resolve();
150+
deferred.notify();
151+
});
152+
Ember.run(function() {
153+
deferred.notify();
154+
});
155+
156+
setTimeout(function() {
157+
start();
158+
equal(count, 3, "progress called three times");
159+
}, 20);
160+
});
161+
162+
test("resolve prevent reject and stop progress", function() {
163+
var deferred, resolved = false, rejected = false, progress = 0;
164+
165+
Ember.run(function() {
166+
deferred = Ember.Object.create(Ember.Deferred);
167+
});
168+
169+
deferred.then(function() {
170+
resolved = true;
171+
}, function() {
172+
rejected = true;
173+
}, function() {
174+
progress++;
175+
});
176+
177+
stop();
178+
Ember.run(function() {
179+
deferred.notify();
180+
});
181+
Ember.run(function() {
182+
deferred.resolve();
183+
});
184+
Ember.run(function() {
185+
deferred.reject();
186+
});
187+
Ember.run(function() {
188+
deferred.notify();
189+
});
190+
191+
setTimeout(function() {
192+
start();
193+
equal(resolved, true, "is resolved");
194+
equal(rejected, false, "is not rejected");
195+
equal(progress, 1, "progress called once");
196+
}, 20);
197+
});
198+
199+
test("reject prevent resolve and stop progress", function() {
200+
var deferred, resolved = false, rejected = false, progress = 0;
201+
202+
Ember.run(function() {
203+
deferred = Ember.Object.create(Ember.Deferred);
204+
});
205+
206+
deferred.then(function() {
207+
resolved = true;
208+
}, function() {
209+
rejected = true;
210+
}, function() {
211+
progress++;
212+
});
213+
214+
stop();
215+
Ember.run(function() {
216+
deferred.notify();
217+
});
218+
Ember.run(function() {
219+
deferred.reject();
220+
});
221+
Ember.run(function() {
222+
deferred.resolve();
223+
});
224+
Ember.run(function() {
225+
deferred.notify();
226+
});
227+
228+
setTimeout(function() {
229+
start();
230+
equal(resolved, false, "is not resolved");
231+
equal(rejected, true, "is rejected");
232+
equal(progress, 1, "progress called once");
233+
}, 20);
234+
});
235+
236+
test("will call callbacks if they are added after resolution", function() {
237+
238+
var deferred, count1 = 0;
239+
240+
Ember.run(function() {
241+
deferred = Ember.Object.create(Ember.Deferred);
242+
});
243+
244+
stop();
245+
Ember.run(function() {
246+
deferred.resolve('toto');
247+
});
248+
249+
Ember.run(function() {
250+
deferred.then(function(context) {
251+
if (context === 'toto') {
252+
count1++;
253+
}
254+
});
255+
256+
deferred.then(function(context) {
257+
if (context === 'toto') {
258+
count1++;
259+
}
260+
});
261+
});
262+
263+
setTimeout(function() {
264+
start();
265+
equal(count1, 2, "callbacks called after resolution");
266+
}, 20);
267+
});

0 commit comments

Comments
 (0)