Index: src/promise.js |
diff --git a/src/promise.js b/src/promise.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b630b3b2345266249efb61b374eb413e72f895de |
--- /dev/null |
+++ b/src/promise.js |
@@ -0,0 +1,293 @@ |
+// 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; |
+ |
+ |
+//------------------------------------------------------------------- |
+ |
+// Core functionality. |
+ |
+// Event queue format: [(value, [(handler, deferred)*])*] |
+// I.e., a list of value/tasks pairs, where the value is a resolution value or |
+// rejection reason, and the tasks are a respective list of handler/deferred |
+// pairs waiting for notification of this value. Each handler is an onResolve or |
+// onReject function provided to the same call of 'when' that produced the |
+// associated deferred. |
+var promiseEvents = new InternalArray; |
+ |
+// 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"); |
+var promiseRaw = NEW_PRIVATE("Promise#raw"); |
+ |
+function IsPromise(x) { |
+ return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); |
+} |
+ |
+function Promise(resolver) { |
+ if (resolver === promiseRaw) return; |
+ var promise = PromiseInit(this); |
+ resolver(function(x) { PromiseResolve(promise, x) }, |
+ function(r) { PromiseReject(promise, r) }); |
+} |
+ |
+function PromiseSet(promise, status, value, onResolve, onReject) { |
+ SET_PRIVATE(promise, promiseStatus, status); |
+ SET_PRIVATE(promise, promiseValue, value); |
+ SET_PRIVATE(promise, promiseOnResolve, onResolve); |
+ SET_PRIVATE(promise, promiseOnReject, onReject); |
+ return promise; |
+} |
+ |
+function PromiseInit(promise) { |
+ return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray) |
+} |
+ |
+function PromiseDone(promise, status, value, promiseQueue) { |
+ if (GET_PRIVATE(promise, promiseStatus) !== 0) |
+ throw MakeTypeError('promise_not_pending', [promise]); |
+ PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); |
+ PromiseSet(promise, status, value); |
+} |
+ |
+function PromiseResolve(promise, x) { |
+ PromiseDone(promise, +1, x, promiseOnResolve) |
+} |
+ |
+function PromiseReject(promise, r) { |
+ PromiseDone(promise, -1, r, promiseOnReject) |
+} |
+ |
+ |
+// Convenience. |
+ |
+function PromiseDeferred() { |
+ if (this === $Promise) { |
+ // Optimized case, avoid extra closure. |
+ var promise = PromiseInit(new Promise(promiseRaw)); |
+ return { |
+ promise: promise, |
+ resolve: function(x) { PromiseResolve(promise, x) }, |
+ reject: function(r) { PromiseReject(promise, r) } |
+ }; |
+ } else { |
+ var result = {}; |
+ result.promise = new this(function(resolve, reject) { |
+ result.resolve = resolve; |
+ result.reject = reject; |
+ }) |
+ return result; |
+ } |
+} |
+ |
+function PromiseResolved(x) { |
+ if (this === $Promise) { |
+ // Optimized case, avoid extra closure. |
+ return PromiseSet(new Promise(promiseRaw), +1, x); |
+ } else { |
+ return new this(function(resolve, reject) { resolve(x) }); |
+ } |
+} |
+ |
+function PromiseRejected(r) { |
+ if (this === $Promise) { |
+ // Optimized case, avoid extra closure. |
+ return PromiseSet(new Promise(promiseRaw), -1, r); |
+ } else { |
+ 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 = %_CallFunction(this.constructor, PromiseDeferred); |
+ switch (GET_PRIVATE(this, promiseStatus)) { |
+ case UNDEFINED: |
+ throw MakeTypeError('not_a_promise', [this]); |
+ case 0: // Pending |
+ GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); |
+ GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); |
+ break; |
+ case +1: // Resolved |
+ PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]); |
+ break; |
+ case -1: // Rejected |
+ PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]); |
+ break; |
+ } |
+ return deferred.promise; |
+} |
+ |
+function PromiseCatch(onReject) { |
+ return this.when(UNDEFINED, onReject); |
+} |
+ |
+function PromiseEnqueue(value, tasks) { |
+ promiseEvents.push(value, tasks); |
+ %SetMicrotasksPending(true); |
+} |
+ |
+function PromiseMicrotasksRunner() { |
+ var events = promiseEvents; |
+ if (events.length > 0) { |
+ promiseEvents = new InternalArray; |
+ for (var i = 0; i < events.length; i += 2) { |
+ var value = events[i]; |
+ var tasks = events[i + 1]; |
+ for (var j = 0; j < tasks.length; j += 2) { |
+ var handler = tasks[j]; |
+ var deferred = tasks[j + 1]; |
+ try { |
+ var result = handler(value); |
+ if (result === deferred.promise) |
+ throw MakeTypeError('promise_cyclic', [result]); |
+ else if (IsPromise(result)) |
+ result.when(deferred.resolve, deferred.reject); |
+ else |
+ deferred.resolve(result); |
+ } catch(e) { |
+ // TODO(rossberg): perhaps log uncaught exceptions below. |
+ try { deferred.reject(e) } catch(e) {} |
+ } |
+ } |
+ } |
+ } |
+} |
+RunMicrotasks.runners.push(PromiseMicrotasksRunner); |
+ |
+ |
+// 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_INLINE(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) { |
+ // TODO(rossberg): cannot do better until we support @@create. |
+ return IsPromise(x) ? x : this.resolved(x); |
+} |
+ |
+function PromiseAll(values) { |
yhirano
2013/11/18 03:59:04
This function returns a promise which will never b
rossberg
2013/11/18 10:56:05
Oops, I somehow managed to misread the spec 8-}, a
yhirano
2013/11/18 11:03:46
Sorry, my comment was wrong.
I wanted to say:
Thi
rossberg
2013/11/18 11:51:09
Ah, excellent point. Fixed and test case added (al
|
+ var deferred = this.deferred(); |
+ var count = 0; |
+ for (var i = 0; i < values.length; ++i) { |
+ ++count; |
+ this.cast(values[i]).when( |
+ function(x) { if (--count === 0) deferred.resolve() }, |
+ 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 = 0; i < values.length; ++i) { |
+ 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, |
yhirano
2013/11/18 03:59:04
Is this Promise.race?
rossberg
2013/11/18 10:56:05
Yes, added a comment.
|
+ "cast", PromiseCast |
+ ]); |
+ InstallFunctions($Promise.prototype, DONT_ENUM, [ |
+ "when", PromiseWhen, |
yhirano
2013/11/18 03:59:04
There is no Promise.prototype.when in the spec dra
yhirano
2013/11/18 04:01:31
Oh, I didn't read the description. Never mind.
|
+ "then", PromiseThen, |
+ "catch", PromiseCatch |
+ ]); |
+} |
+ |
+SetUpPromise(); |