Index: src/promise.js |
diff --git a/src/promise.js b/src/promise.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..23801801cd6d3c6726d1661b64592b03dd841100 |
--- /dev/null |
+++ b/src/promise.js |
@@ -0,0 +1,271 @@ |
+// Copyright 2012 the V8 project authors. All rights reserved. |
+// Redistribution and use in source and binary forms, with or without |
+// modification, are permitted provided that the following conditions are |
+// met: |
+// |
+// * Redistributions of source code must retain the above copyright |
+// notice, this list of conditions and the following disclaimer. |
+// * Redistributions in binary form must reproduce the above |
+// copyright notice, this list of conditions and the following |
+// disclaimer in the documentation and/or other materials provided |
+// with the distribution. |
+// * Neither the name of Google Inc. nor the names of its |
+// contributors may be used to endorse or promote products derived |
+// from this software without specific prior written permission. |
+// |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ |
+ |
+"use strict"; |
+ |
+//This file relies on the fact that the following declaration has been made |
+//in runtime.js: |
+//var $Object = global.Object; |
+//var $WeakMap = global.WeakMap; |
+ |
+ |
+var $Promise = Promise; |
+ |
+ |
+// TODO(rossberg): A temporary shim for creating microtasks. |
+function Task(task) { |
+ var dummy = {} |
+ $Object.observe(dummy, task) |
+ dummy.dummy = dummy |
+} |
+ |
+ |
+//------------------------------------------------------------------- |
+ |
+// Core functionality. |
+ |
+// Status values: 0 = pending, +1 = resolved, -1 = rejected |
+var promiseStatus = NEW_PRIVATE("Promise#status"); |
+var promiseValue = NEW_PRIVATE("Promise#value"); |
+var promiseOnResolve = NEW_PRIVATE("Promise#onResolve"); |
+var promiseOnReject = NEW_PRIVATE("Promise#onReject"); |
+ |
+function IsPromise(x) { |
+ return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); |
+} |
+ |
+function Promise(resolver) { |
+ SET_PRIVATE(this, promiseStatus, 0); |
+ SET_PRIVATE(this, promiseOnResolve, []); |
+ SET_PRIVATE(this, promiseOnReject, []); |
+ var that = this; |
+ resolver(function(x) { PromiseResolve(that, x) }, |
+ function(r) { PromiseReject(that, r) }); |
+} |
+ |
+function PromiseResolve(promise, x) { |
+ if (GET_PRIVATE(promise, promiseStatus) !== 0) |
+ throw MakeTypeError('promise_not_pending', [promise]); |
+ PromiseQueue(GET_PRIVATE(promise, promiseOnResolve), x); |
+ SET_PRIVATE(promise, promiseValue, x); |
+ SET_PRIVATE(promise, promiseOnResolve, UNDEFINED); |
+ SET_PRIVATE(promise, promiseOnReject, UNDEFINED); |
+ SET_PRIVATE(promise, promiseStatus, +1); |
+} |
+ |
+function PromiseReject(promise, r) { |
+ if (GET_PRIVATE(promise, promiseStatus) !== 0) |
+ throw MakeTypeError('promise_not_pending', [promise]); |
+ PromiseQueue(GET_PRIVATE(promise, promiseOnReject), r); |
+ SET_PRIVATE(promise, promiseValue, r); |
+ SET_PRIVATE(promise, promiseOnResolve, UNDEFINED); |
+ SET_PRIVATE(promise, promiseOnReject, UNDEFINED); |
+ SET_PRIVATE(promise, promiseStatus, -1); |
+} |
+ |
+function PromiseQueue(tasks, x) { |
+ for (var i in tasks) { |
+ Task(function() { tasks[i](x) }); |
+ } |
+} |
+ |
+ |
+// Convenience. |
+ |
+function PromiseDeferred() { |
+ var result = {}; |
+ result.promise = new this(function(resolve, reject) { |
+ result.resolve = resolve; |
+ result.reject = reject; |
+ }) |
+ return result; |
+} |
+ |
+function PromiseResolved(x) { |
+ return new this(function(resolve, reject) { resolve(x) }); |
+} |
+ |
+function PromiseRejected(r) { |
+ return new this(function(resolve, reject) { reject(r) }); |
+} |
+ |
+ |
+// Simple chaining (a.k.a. flatMap). |
+ |
+function PromiseNopHandler() {} |
+ |
+function PromiseWhen(onResolve, onReject) { |
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; |
+ onReject = IS_UNDEFINED(onReject) ? PromiseNopHandler : onReject; |
+ var deferred = Promise.deferred.call(this.constructor); |
+ switch (GET_PRIVATE(this, promiseStatus)) { |
+ case UNDEFINED: |
+ throw MakeTypeError('not_a_promise', [this]); |
+ case 0: // Pending |
+ GET_PRIVATE(this, promiseOnResolve).push( |
+ PromiseChain(deferred, onResolve)); |
+ GET_PRIVATE(this, promiseOnReject).push( |
+ PromiseChain(deferred, onReject)); |
+ break |
+ case +1: // Resolved |
+ PromiseQueue([PromiseChain(deferred, onResolve)], |
+ GET_PRIVATE(this, promiseValue)); |
+ break |
+ case -1: // Rejected |
+ PromiseQueue([PromiseChain(deferred, onReject)], |
+ GET_PRIVATE(this, promiseValue)); |
+ break |
+ default: |
+ unreachable(); |
+ } |
+ return deferred.promise; |
+} |
+ |
+function PromiseCatch(onReject) { |
+ return this.when(UNDEFINED, onReject); |
+} |
+ |
+// TODO(rossberg): uncurry |
+function PromiseChain(deferred, handler) { |
+ return function(x) { |
+ try { |
+ var y = handler(x); |
+ if (y === deferred.promise) |
+ throw MakeTypeError('promise_cyclic', [y]); |
+ else if (IsPromise(y)) |
+ y.when(deferred.resolve, deferred.reject); |
+ else |
+ deferred.resolve(y); |
+ } catch(e) { |
+ deferred.reject(e); |
+ } |
+ } |
+} |
+ |
+ |
+// Extended functionality for multi-unwrapping chaining and coercive 'then'. |
+ |
+function PromiseThen(onResolve, onReject) { |
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve |
+ var that = this |
+ var constructor = this.constructor |
+ return this.when( |
+ function(x) { |
+ x = PromiseCoerce(constructor, x) |
+ return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : |
+ IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x) |
+ }, |
+ onReject |
+ ) |
+} |
+ |
+PromiseCoerce.table = new $WeakMap |
+ |
+function PromiseCoerce(constructor, x) { |
+ if (IsPromise(x)) { |
+ return x |
+ } else if (!IS_NULL_OR_UNDEFINED(x) && 'then' in TO_OBJECT(x)) { |
+ if (PromiseCoerce.table.has(x)) { |
+ return PromiseCoerce.table.get(x) |
+ } else { |
+ var deferred = constructor.deferred() |
+ PromiseCoerce.table.set(x, deferred.promise) |
+ try { |
+ x.then(deferred.resolve, deferred.reject) |
+ } catch(e) { |
+ deferred.reject(e) |
+ } |
+ return deferred.promise |
+ } |
+ } else { |
+ return x |
+ } |
+} |
+ |
+ |
+// Combinators. |
+ |
+function PromiseCast(x) { |
+ if (x instanceof this) return x |
+ if (IsPromise(x)) { |
+ var result = this.deferred() |
+ x.when(result.resolve, result.reject) |
+ return result.promise |
+ } |
+ return this.resolved(x) |
+} |
+ |
+function PromiseAll(values) { |
+ var deferred = this.deferred() |
+ var count = 0 |
+ for (var i in values) { |
+ ++count |
+ this.cast(values[i]).when( |
+ function(x) { if (--count === 0) deferred.resolve(undefined) }, |
+ function(r) { if (count > 0) { count = 0; deferred.reject(r) } } |
+ ) |
+ } |
+ return deferred.promise |
+} |
+ |
+function PromiseOne(values) { |
+ var deferred = this.deferred() |
+ var done = false |
+ for (var i in values) { |
+ this.cast(values[i]).when( |
+ function(x) { if (!done) { done = true; deferred.resolve(x) } }, |
+ function(r) { if (!done) { done = true; deferred.reject(r) } } |
+ ) |
+ } |
+ return deferred.promise |
+} |
+ |
+//------------------------------------------------------------------- |
+ |
+function SetUpPromise() { |
+ %CheckIsBootstrapping() |
+ |
+ global.Promise = $Promise; |
+ |
+ InstallFunctions($Promise, DONT_ENUM, [ |
+ "deferred", PromiseDeferred, |
+ "resolved", PromiseResolved, |
+ "rejected", PromiseRejected, |
+ "all", PromiseAll, |
+ "one", PromiseOne, |
+ "cast", PromiseCast |
+ ]) |
+ |
+ InstallFunctions($Promise.prototype, DONT_ENUM, [ |
+ "when", PromiseWhen, |
+ "then", PromiseThen, |
+ "catch", PromiseCatch |
+ ]) |
+} |
+ |
+SetUpPromise(); |