diff options
Diffstat (limited to 'chromium/v8/src/promise.js')
-rw-r--r-- | chromium/v8/src/promise.js | 540 |
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(); +})(); |