2 // Alex Russell <slightlyoff@chromium.org>
5 // Use of this source code is governed by
6 // http://www.apache.org/licenses/LICENSE-2.0
9 // - Document "npm test"
10 // - Change global name from "Promise" to something less conflicty
11 (function(global, browserGlobal, underTest) {
14 // FIXME(slighltyoff):
15 // * aggregates + tests
16 // * check on fast-forwarding
18 underTest = !!underTest;
24 // Borrowed from RSVP.js
27 var MutationObserver = browserGlobal.MutationObserver ||
28 browserGlobal.WebKitMutationObserver;
31 if (typeof process !== 'undefined' &&
32 {}.toString.call(process) === '[object process]') {
33 async = function(callback, binding) {
34 process.nextTick(function() {
35 callback.call(binding);
38 } else if (MutationObserver) {
41 var observer = new MutationObserver(function() {
42 var toProcess = queue.slice();
44 toProcess.forEach(function(tuple) {
45 tuple[0].call(tuple[1]);
49 var element = document.createElement('div');
50 observer.observe(element, { attributes: true });
52 // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
53 window.addEventListener('unload', function(){
54 observer.disconnect();
58 async = function(callback, binding) {
59 queue.push([callback, binding]);
60 element.setAttribute('drainQueue', 'drainQueue');
63 async = function(callback, binding) {
64 setTimeout(function() {
65 callback.call(binding);
71 // Object Model Utilities
74 // defineProperties utilities
75 var _readOnlyProperty = function(v) {
83 var _method = function(v, e, c, w) {
85 enumerable: !!(e || 0),
86 configurable: !!(c || 1),
88 value: v || function() {}
92 var _pseudoPrivate = function(v) { return _method(v, 0, 1, 0); };
93 var _public = function(v) { return _method(v, 1); };
99 var isThenable = function(any) {
100 if (any === undefined)
104 if (typeof f == "function") {
107 } catch (e) { /*squelch*/ }
111 var AlreadyResolved = function(name) {
112 Error.call(this, name);
114 AlreadyResolved.prototype = Object.create(Error.prototype);
116 var Backlog = function() {
118 bl.pump = function(value) {
132 // Resolver Constuctor
135 var Resolver = function(future,
141 var isResolved = false;
144 var fulfill = function(value) {
145 // console.log("queueing fulfill with:", value);
147 setState("fulfilled");
149 // console.log("fulfilling with:", value);
150 fulfillCallbacks.pump(value);
153 var reject = function(reason) {
154 // console.log("queuing reject with:", reason);
156 setState("rejected");
158 // console.log("rejecting with:", reason);
159 rejectCallbacks.pump(reason);
162 var resolve = function(value) {
163 if (isThenable(value)) {
164 value.then(resolve, reject);
169 var ifNotResolved = function(func, name) {
170 return function(value) {
175 if (typeof console != "undefined") {
176 console.error("Cannot resolve a Promise multiple times.");
182 // Indirectly resolves the Promise, chaining any passed Promise's resolution
183 this.resolve = ifNotResolved(resolve, "resolve");
185 // Directly fulfills the future, no matter what value's type is
186 this.fulfill = ifNotResolved(fulfill, "fulfill");
188 // Rejects the future
189 this.reject = ifNotResolved(reject, "reject");
191 this.cancel = function() { resolver.reject(new Error("Cancel")); };
192 this.timeout = function() { resolver.reject(new Error("Timeout")); };
195 Object.defineProperties(this, {
196 _isResolved: _readOnlyProperty(function() { return isResolved; }),
204 // Promise Constuctor
207 var Promise = function(init) {
208 var fulfillCallbacks = new Backlog();
209 var rejectCallbacks = new Backlog();
212 var state = "pending";
215 Object.defineProperties(this, {
216 _value: _readOnlyProperty(function() { return value; }),
217 _error: _readOnlyProperty(function() { return error; }),
218 _state: _readOnlyProperty(function() { return state; }),
222 Object.defineProperties(this, {
223 _addAcceptCallback: _pseudoPrivate(
225 // console.log("adding fulfill callback:", cb);
226 fulfillCallbacks.push(cb);
227 if (state == "fulfilled") {
228 fulfillCallbacks.pump(value);
232 _addRejectCallback: _pseudoPrivate(
234 // console.log("adding reject callback:", cb);
235 rejectCallbacks.push(cb);
236 if (state == "rejected") {
237 rejectCallbacks.pump(error);
242 var r = new Resolver(this,
243 fulfillCallbacks, rejectCallbacks,
244 function(v) { value = v; },
245 function(e) { error = e; },
246 function(s) { state = s; })
248 if (init) { init(r); }
251 console.log(e.stack);
259 var isCallback = function(any) {
260 return (typeof any == "function");
264 var wrap = function(callback, resolver, disposition) {
265 if (!isCallback(callback)) {
266 // If we don't get a callback, we want to forward whatever resolution we get
267 return resolver[disposition].bind(resolver);
272 var r = callback.apply(null, arguments);
275 // Exceptions reject the resolver
277 console.log(e.stack);
282 var addCallbacks = function(onfulfill, onreject, scope) {
283 if (isCallback(onfulfill)) {
284 scope._addAcceptCallback(onfulfill);
286 if (isCallback(onreject)) {
287 scope._addRejectCallback(onreject);
293 // Prototype properties
296 Promise.prototype = Object.create(null, {
297 "then": _public(function(onfulfill, onreject) {
298 // The logic here is:
299 // We return a new Promise whose resolution merges with the return from
300 // onfulfill() or onerror(). If onfulfill() returns a Promise, we forward
301 // the resolution of that future to the resolution of the returned
304 return new Promise(function(r) {
305 addCallbacks(wrap(onfulfill, r, "resolve"),
306 wrap(onreject, r, "reject"), f);
309 "catch": _public(function(onreject) {
311 return new Promise(function(r) {
312 addCallbacks(null, wrap(onreject, r, "reject"), f);
321 Promise.isThenable = isThenable;
323 var toPromiseList = function(list) {
324 return Array.prototype.slice.call(list).map(Promise.resolve);
327 Promise.any = function(/*...futuresOrValues*/) {
328 var futures = toPromiseList(arguments);
329 return new Promise(function(r) {
330 if (!futures.length) {
331 r.reject("No futures passed to Promise.any()");
333 var resolved = false;
334 var firstSuccess = function(value) {
335 if (resolved) { return; }
339 var firstFailure = function(reason) {
340 if (resolved) { return; }
344 futures.forEach(function(f, idx) {
345 f.then(firstSuccess, firstFailure);
351 Promise.every = function(/*...futuresOrValues*/) {
352 var futures = toPromiseList(arguments);
353 return new Promise(function(r) {
354 if (!futures.length) {
355 r.reject("No futures passed to Promise.every()");
357 var values = new Array(futures.length);
359 var accumulate = function(idx, v) {
362 if (count == futures.length) {
366 futures.forEach(function(f, idx) {
367 f.then(accumulate.bind(null, idx), r.reject);
373 Promise.some = function() {
374 var futures = toPromiseList(arguments);
375 return new Promise(function(r) {
376 if (!futures.length) {
377 r.reject("No futures passed to Promise.some()");
380 var accumulateFailures = function(e) {
382 if (count == futures.length) {
386 futures.forEach(function(f, idx) {
387 f.then(r.resolve, accumulateFailures);
393 Promise.fulfill = function(value) {
394 return new Promise(function(r) {
399 Promise.resolve = function(value) {
400 return new Promise(function(r) {
405 Promise.reject = function(reason) {
406 return new Promise(function(r) {
415 global.Promise = Promise;
418 (typeof window !== 'undefined') ? window : {},
419 this.runningUnderTest||false);