summaryrefslogtreecommitdiffstats
path: root/chromium/v8/src/promise.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/src/promise.js')
-rw-r--r--chromium/v8/src/promise.js540
1 files changed, 276 insertions, 264 deletions
diff --git a/chromium/v8/src/promise.js b/chromium/v8/src/promise.js
index 30f4f07b4b7..710abadbeb8 100644
--- a/chromium/v8/src/promise.js
+++ b/chromium/v8/src/promise.js
@@ -1,30 +1,6 @@
// 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 of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
"use strict";
@@ -33,273 +9,309 @@
// var $Object = global.Object
// var $WeakMap = global.WeakMap
+// For bootstrapper.
-var $Promise = Promise;
-
-
-//-------------------------------------------------------------------
+var IsPromise;
+var PromiseCreate;
+var PromiseResolve;
+var PromiseReject;
+var PromiseChain;
+var PromiseCatch;
+var PromiseThen;
-// 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 'chain' that produced the
-// associated deferred.
-var promiseEvents = new InternalArray;
+// mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
+// if we could move these property names into the closure below.
+// TODO(jkummerow/rossberg/yangguo): Find a better solution.
// 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) });
- // TODO(rossberg): current draft makes exception from this call asynchronous,
- // but that's probably a mistake.
-}
-
-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) return;
- 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;
+var promiseStatus = GLOBAL_PRIVATE("Promise#status");
+var promiseValue = GLOBAL_PRIVATE("Promise#value");
+var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
+var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
+var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
+
+(function() {
+
+ var $Promise = function Promise(resolver) {
+ if (resolver === promiseRaw) return;
+ if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
+ if (!IS_SPEC_FUNCTION(resolver))
+ throw MakeTypeError('resolver_not_a_function', [resolver]);
+ var promise = PromiseInit(this);
+ try {
+ %DebugPromiseHandlePrologue(function() { return promise });
+ resolver(function(x) { PromiseResolve(promise, x) },
+ function(r) { PromiseReject(promise, r) });
+ } catch (e) {
+ PromiseReject(promise, e);
+ } finally {
+ %DebugPromiseHandleEpilogue();
+ }
}
-}
-
-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) });
+
+ // Core functionality.
+
+ 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 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) });
+
+ function PromiseInit(promise) {
+ return PromiseSet(
+ promise, 0, UNDEFINED, new InternalArray, new InternalArray)
}
-}
-
-
-// Simple chaining.
-
-function PromiseIdResolveHandler(x) { return x }
-function PromiseIdRejectHandler(r) { throw r }
-
-function PromiseChain(onResolve, onReject) { // a.k.a. flatMap
- onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
- onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : 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;
+
+ function PromiseDone(promise, status, value, promiseQueue) {
+ if (GET_PRIVATE(promise, promiseStatus) === 0) {
+ PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
+ PromiseSet(promise, status, value);
+ }
}
- return deferred.promise;
-}
-
-function PromiseCatch(onReject) {
- return this.chain(UNDEFINED, onReject);
-}
-
-function PromiseEnqueue(value, tasks) {
- promiseEvents.push(value, tasks);
- %SetMicrotaskPending(true);
-}
-
-function PromiseMicrotaskRunner() {
- 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];
+
+ function PromiseCoerce(constructor, x) {
+ if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
+ var then;
+ try {
+ then = x.then;
+ } catch(r) {
+ return %_CallFunction(constructor, r, PromiseRejected);
+ }
+ if (IS_SPEC_FUNCTION(then)) {
+ var deferred = %_CallFunction(constructor, PromiseDeferred);
try {
- var result = handler(value);
- if (result === deferred.promise)
- throw MakeTypeError('promise_cyclic', [result]);
- else if (IsPromise(result))
- result.chain(deferred.resolve, deferred.reject);
- else
- deferred.resolve(result);
- } catch(e) {
- // TODO(rossberg): perhaps log uncaught exceptions below.
- try { deferred.reject(e) } catch(e) {}
+ %_CallFunction(x, deferred.resolve, deferred.reject, then);
+ } catch(r) {
+ deferred.reject(r);
}
+ return deferred.promise;
}
}
- }
-}
-RunMicrotasks.runners.push(PromiseMicrotaskRunner);
-
-
-// Multi-unwrapped chaining with thenable coercion.
-
-function PromiseThen(onResolve, onReject) {
- onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
- var that = this;
- var constructor = this.constructor;
- return this.chain(
- 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) {
- var then;
- if (IsPromise(x)) {
return x;
- } else if (!IS_NULL_OR_UNDEFINED(x) && %IsCallable(then = x.then)) {
- if (PromiseCoerce.table.has(x)) {
- return PromiseCoerce.table.get(x);
- } else {
- var deferred = constructor.deferred();
- PromiseCoerce.table.set(x, deferred.promise);
+ }
+
+ function PromiseHandle(value, handler, deferred) {
+ try {
+ %DebugPromiseHandlePrologue(
+ function() {
+ var queue = GET_PRIVATE(deferred.promise, promiseOnReject);
+ return (queue && queue.length == 0) ? deferred.promise : UNDEFINED;
+ });
+ var result = handler(value);
+ if (result === deferred.promise)
+ throw MakeTypeError('promise_cyclic', [result]);
+ else if (IsPromise(result))
+ %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
+ else
+ deferred.resolve(result);
+ } catch (exception) {
try {
- %_CallFunction(x, deferred.resolve, deferred.reject, then);
- } catch(e) {
- deferred.reject(e);
+ %DebugPromiseHandlePrologue(function() { return deferred.promise });
+ deferred.reject(exception);
+ } catch (e) { } finally {
+ %DebugPromiseHandleEpilogue();
}
- return deferred.promise;
+ } finally {
+ %DebugPromiseHandleEpilogue();
}
- } 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) {
- var deferred = this.deferred();
- var resolutions = [];
- var count = values.length;
- if (count === 0) {
- deferred.resolve(resolutions);
- } else {
- for (var i = 0; i < values.length; ++i) {
- this.cast(values[i]).chain(
- function(i, x) {
- resolutions[i] = x;
- if (--count === 0) deferred.resolve(resolutions);
- }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once available
- function(r) {
- if (count > 0) { count = 0; deferred.reject(r) }
- }
- );
+
+ function PromiseEnqueue(value, tasks) {
+ %EnqueueMicrotask(function() {
+ for (var i = 0; i < tasks.length; i += 2) {
+ PromiseHandle(value, tasks[i], tasks[i + 1])
+ }
+ });
+ }
+
+ function PromiseIdResolveHandler(x) { return x }
+ function PromiseIdRejectHandler(r) { throw r }
+
+ function PromiseNopResolver() {}
+
+ // -------------------------------------------------------------------
+ // Define exported functions.
+
+ // For bootstrapper.
+
+ IsPromise = function IsPromise(x) {
+ return IS_SPEC_OBJECT(x) && HAS_PRIVATE(x, promiseStatus);
+ }
+
+ PromiseCreate = function PromiseCreate() {
+ return new $Promise(PromiseNopResolver)
+ }
+
+ PromiseResolve = function PromiseResolve(promise, x) {
+ PromiseDone(promise, +1, x, promiseOnResolve)
+ }
+
+ PromiseReject = 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;
}
}
- return deferred.promise;
-}
-
-function PromiseOne(values) { // a.k.a. race
- var deferred = this.deferred();
- var done = false;
- for (var i = 0; i < values.length; ++i) {
- this.cast(values[i]).chain(
- function(x) { if (!done) { done = true; deferred.resolve(x) } },
- function(r) { if (!done) { done = true; deferred.reject(r) } }
+
+ 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.
+
+ PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a.
+ // flatMap
+ onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
+ onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : 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;
+ }
+
+ PromiseCatch = function PromiseCatch(onReject) {
+ return this.then(UNDEFINED, onReject);
+ }
+
+ // Multi-unwrapped chaining with thenable coercion.
+
+ PromiseThen = function PromiseThen(onResolve, onReject) {
+ onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
+ : PromiseIdResolveHandler;
+ onReject = IS_SPEC_FUNCTION(onReject) ? onReject
+ : PromiseIdRejectHandler;
+ var that = this;
+ var constructor = this.constructor;
+ return %_CallFunction(
+ this,
+ function(x) {
+ x = PromiseCoerce(constructor, x);
+ return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
+ IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
+ },
+ onReject,
+ PromiseChain
);
}
- return deferred.promise;
-}
-//-------------------------------------------------------------------
+ // Combinators.
+
+ function PromiseCast(x) {
+ // TODO(rossberg): cannot do better until we support @@create.
+ return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
+ }
+
+ function PromiseAll(values) {
+ var deferred = %_CallFunction(this, PromiseDeferred);
+ var resolutions = [];
+ if (!%_IsArray(values)) {
+ deferred.reject(MakeTypeError('invalid_argument'));
+ return deferred.promise;
+ }
+ try {
+ var count = values.length;
+ if (count === 0) {
+ deferred.resolve(resolutions);
+ } else {
+ for (var i = 0; i < values.length; ++i) {
+ this.resolve(values[i]).then(
+ function(i, x) {
+ resolutions[i] = x;
+ if (--count === 0) deferred.resolve(resolutions);
+ }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once
+ // available
+ function(r) { deferred.reject(r) }
+ );
+ }
+ }
+ } catch (e) {
+ deferred.reject(e)
+ }
+ return deferred.promise;
+ }
+
+ function PromiseOne(values) {
+ var deferred = %_CallFunction(this, PromiseDeferred);
+ if (!%_IsArray(values)) {
+ deferred.reject(MakeTypeError('invalid_argument'));
+ return deferred.promise;
+ }
+ try {
+ for (var i = 0; i < values.length; ++i) {
+ this.resolve(values[i]).then(
+ function(x) { deferred.resolve(x) },
+ function(r) { deferred.reject(r) }
+ );
+ }
+ } catch (e) {
+ deferred.reject(e)
+ }
+ return deferred.promise;
+ }
+
+ // -------------------------------------------------------------------
+ // Install exported functions.
-function SetUpPromise() {
- %CheckIsBootstrapping()
- global.Promise = $Promise;
+ %CheckIsBootstrapping();
+ %SetProperty(global, 'Promise', $Promise, DONT_ENUM);
InstallFunctions($Promise, DONT_ENUM, [
- "deferred", PromiseDeferred,
- "resolved", PromiseResolved,
- "rejected", PromiseRejected,
+ "defer", PromiseDeferred,
+ "accept", PromiseResolved,
+ "reject", PromiseRejected,
"all", PromiseAll,
- "one", PromiseOne,
- "cast", PromiseCast
+ "race", PromiseOne,
+ "resolve", PromiseCast
]);
InstallFunctions($Promise.prototype, DONT_ENUM, [
"chain", PromiseChain,
"then", PromiseThen,
"catch", PromiseCatch
]);
-}
-SetUpPromise();
+})();