Chromium Code Reviews| 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(); |