diff options
Diffstat (limited to 'deps/v8/src/js/promise.js')
-rw-r--r-- | deps/v8/src/js/promise.js | 305 |
1 files changed, 184 insertions, 121 deletions
diff --git a/deps/v8/src/js/promise.js b/deps/v8/src/js/promise.js index d7e9a5c67f..8cf6a36cef 100644 --- a/deps/v8/src/js/promise.js +++ b/deps/v8/src/js/promise.js @@ -23,10 +23,12 @@ var promiseOnResolveSymbol = var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); +var SpeciesConstructor; var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); utils.Import(function(from) { MakeTypeError = from.MakeTypeError; + SpeciesConstructor = from.SpeciesConstructor; }); // ------------------------------------------------------------------- @@ -34,21 +36,50 @@ utils.Import(function(from) { // Status values: 0 = pending, +1 = resolved, -1 = rejected var lastMicrotaskId = 0; +function CreateResolvingFunctions(promise) { + var alreadyResolved = false; + + var resolve = value => { + if (alreadyResolved === true) return; + alreadyResolved = true; + PromiseResolve(promise, value); + }; + + var reject = reason => { + if (alreadyResolved === true) return; + alreadyResolved = true; + PromiseReject(promise, reason); + }; + + return { + __proto__: null, + resolve: resolve, + reject: reject + }; +} + + var GlobalPromise = function Promise(resolver) { - if (resolver === promiseRawSymbol) return; - if (!%_IsConstructCall()) throw MakeTypeError(kNotAPromise, this); + if (resolver === promiseRawSymbol) { + return %NewObject(GlobalPromise, new.target); + } + if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this); if (!IS_CALLABLE(resolver)) throw MakeTypeError(kResolverNotAFunction, resolver); - var promise = PromiseInit(this); + + var promise = PromiseInit(%NewObject(GlobalPromise, new.target)); + var callbacks = CreateResolvingFunctions(promise); + try { - %DebugPushPromise(promise, Promise, resolver); - resolver(function(x) { PromiseResolve(promise, x) }, - function(r) { PromiseReject(promise, r) }); + %DebugPushPromise(promise, Promise); + resolver(callbacks.resolve, callbacks.reject); } catch (e) { - PromiseReject(promise, e); + %_Call(callbacks.reject, UNDEFINED, e); } finally { %DebugPopPromise(); } + + return promise; } // Core functionality. @@ -84,37 +115,11 @@ function PromiseDone(promise, status, value, promiseQueue) { } } -function PromiseCoerce(constructor, x) { - if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { - var then; - try { - then = x.then; - } catch(r) { - return %_Call(PromiseRejected, constructor, r); - } - if (IS_CALLABLE(then)) { - var deferred = %_Call(PromiseDeferred, constructor); - try { - %_Call(then, x, deferred.resolve, deferred.reject); - } catch(r) { - deferred.reject(r); - } - return deferred.promise; - } - } - return x; -} - function PromiseHandle(value, handler, deferred) { try { - %DebugPushPromise(deferred.promise, PromiseHandle, handler); + %DebugPushPromise(deferred.promise, PromiseHandle); var result = handler(value); - if (result === deferred.promise) - throw MakeTypeError(kPromiseCyclic, result); - else if (IsPromise(result)) - %_Call(PromiseChain, result, deferred.resolve, deferred.reject); - else - deferred.resolve(result); + deferred.resolve(result); } catch (exception) { try { deferred.reject(exception); } catch (e) { } } finally { @@ -153,7 +158,7 @@ function PromiseNopResolver() {} // For bootstrapper. function IsPromise(x) { - return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); + return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); } function PromiseCreate() { @@ -161,7 +166,42 @@ function PromiseCreate() { } function PromiseResolve(promise, x) { - PromiseDone(promise, +1, x, promiseOnResolveSymbol) + if (x === promise) { + return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); + } + if (IS_RECEIVER(x)) { + // 25.4.1.3.2 steps 8-12 + try { + var then = x.then; + } catch (e) { + return PromiseReject(promise, e); + } + if (IS_CALLABLE(then)) { + // PromiseResolveThenableJob + var id, name, instrumenting = DEBUG_IS_ACTIVE; + %EnqueueMicrotask(function() { + if (instrumenting) { + %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); + } + var callbacks = CreateResolvingFunctions(promise); + try { + %_Call(then, x, callbacks.resolve, callbacks.reject); + } catch (e) { + %_Call(callbacks.reject, UNDEFINED, e); + } + if (instrumenting) { + %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); + } + }); + if (instrumenting) { + id = ++lastMicrotaskId; + name = "PromseResolveThenableJob"; + %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); + } + return; + } + } + PromiseDone(promise, +1, x, promiseOnResolveSymbol); } function PromiseReject(promise, r) { @@ -179,57 +219,70 @@ function PromiseReject(promise, r) { // Convenience. -function PromiseDeferred() { - if (this === GlobalPromise) { +function NewPromiseCapability(C) { + if (C === GlobalPromise) { // Optimized case, avoid extra closure. var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); + var callbacks = CreateResolvingFunctions(promise); return { promise: promise, - resolve: function(x) { PromiseResolve(promise, x) }, - reject: function(r) { PromiseReject(promise, r) } + resolve: callbacks.resolve, + reject: callbacks.reject }; - } else { - var result = {promise: UNDEFINED, reject: UNDEFINED, resolve: UNDEFINED}; - result.promise = new this(function(resolve, reject) { - result.resolve = resolve; - result.reject = reject; - }); - return result; } + + var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; + result.promise = new C((resolve, reject) => { + if (!IS_UNDEFINED(result.resolve) || !IS_UNDEFINED(result.reject)) + throw MakeTypeError(kPromiseExecutorAlreadyInvoked); + result.resolve = resolve; + result.reject = reject; + }); + + return result; +} + +function PromiseDeferred() { + %IncrementUseCounter(kPromiseDefer); + return NewPromiseCapability(this); } function PromiseResolved(x) { - if (this === GlobalPromise) { - // Optimized case, avoid extra closure. - return PromiseCreateAndSet(+1, x); - } else { - return new this(function(resolve, reject) { resolve(x) }); - } + %IncrementUseCounter(kPromiseAccept); + return %_Call(PromiseCast, this, x); } function PromiseRejected(r) { - var promise; + if (!IS_RECEIVER(this)) { + throw MakeTypeError(kCalledOnNonObject, PromiseRejected); + } if (this === GlobalPromise) { // Optimized case, avoid extra closure. - promise = PromiseCreateAndSet(-1, r); + var promise = PromiseCreateAndSet(-1, r); // The debug event for this would always be an uncaught promise reject, // which is usually simply noise. Do not trigger that debug event. %PromiseRejectEvent(promise, r, false); + return promise; } else { - promise = new this(function(resolve, reject) { reject(r) }); + var promiseCapability = NewPromiseCapability(this); + %_Call(promiseCapability.reject, UNDEFINED, r); + return promiseCapability.promise; } - return promise; } -// Simple chaining. +// Multi-unwrapped chaining with thenable coercion. -function PromiseChain(onResolve, onReject) { // a.k.a. flatMap - onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; - onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; - var deferred = %_Call(PromiseDeferred, this.constructor); - switch (GET_PRIVATE(this, promiseStatusSymbol)) { - case UNDEFINED: - throw MakeTypeError(kNotAPromise, this); +function PromiseThen(onResolve, onReject) { + var status = GET_PRIVATE(this, promiseStatusSymbol); + if (IS_UNDEFINED(status)) { + throw MakeTypeError(kNotAPromise, this); + } + + var constructor = SpeciesConstructor(this, GlobalPromise); + onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; + onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; + var deferred = NewPromiseCapability(constructor); + switch (status) { case 0: // Pending GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); @@ -258,85 +311,88 @@ function PromiseChain(onResolve, onReject) { // a.k.a. flatMap return deferred.promise; } -function PromiseCatch(onReject) { - return this.then(UNDEFINED, onReject); +// Chain is left around for now as an alias for then +function PromiseChain(onResolve, onReject) { + %IncrementUseCounter(kPromiseChain); + return %_Call(PromiseThen, this, onResolve, onReject); } -// Multi-unwrapped chaining with thenable coercion. - -function PromiseThen(onResolve, onReject) { - onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; - onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; - var that = this; - var constructor = this.constructor; - return %_Call( - PromiseChain, - this, - function(x) { - x = PromiseCoerce(constructor, x); - if (x === that) { - DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); - return onReject(MakeTypeError(kPromiseCyclic, x)); - } else if (IsPromise(x)) { - return x.then(onResolve, onReject); - } else { - DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); - return onResolve(x); - } - }, - onReject - ); +function PromiseCatch(onReject) { + return this.then(UNDEFINED, onReject); } // Combinators. function PromiseCast(x) { - if (IsPromise(x) && x.constructor === this) { - return x; - } else { - return new this(function(resolve) { resolve(x) }); + if (!IS_RECEIVER(this)) { + throw MakeTypeError(kCalledOnNonObject, PromiseCast); } + if (IsPromise(x) && x.constructor === this) return x; + + var promiseCapability = NewPromiseCapability(this); + var resolveResult = %_Call(promiseCapability.resolve, UNDEFINED, x); + return promiseCapability.promise; } function PromiseAll(iterable) { - var deferred = %_Call(PromiseDeferred, this); - var resolutions = []; + if (!IS_RECEIVER(this)) { + throw MakeTypeError(kCalledOnNonObject, "Promise.all"); + } + + var deferred = NewPromiseCapability(this); + var resolutions = new InternalArray(); + var count; + + function CreateResolveElementFunction(index, values, promiseCapability) { + var alreadyCalled = false; + return (x) => { + if (alreadyCalled === true) return; + alreadyCalled = true; + values[index] = x; + if (--count === 0) { + var valuesArray = []; + %MoveArrayContents(values, valuesArray); + %_Call(promiseCapability.resolve, UNDEFINED, valuesArray); + } + }; + } + try { - var count = 0; var i = 0; + count = 1; for (var value of iterable) { - var reject = function(r) { deferred.reject(r) }; - this.resolve(value).then( - // Nested scope to get closure over current i. - // TODO(arv): Use an inner let binding once available. - (function(i) { - return function(x) { - resolutions[i] = x; - if (--count === 0) deferred.resolve(resolutions); - } - })(i), reject); - SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred); - ++i; + var nextPromise = this.resolve(value); ++count; + nextPromise.then( + CreateResolveElementFunction(i, resolutions, deferred), + deferred.reject); + SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); + ++i; } - if (count === 0) { - deferred.resolve(resolutions); + // 6.d + if (--count === 0) { + var valuesArray = []; + %MoveArrayContents(resolutions, valuesArray); + %_Call(deferred.resolve, UNDEFINED, valuesArray); } } catch (e) { - deferred.reject(e) + %_Call(deferred.reject, UNDEFINED, e); } return deferred.promise; } function PromiseRace(iterable) { - var deferred = %_Call(PromiseDeferred, this); + if (!IS_RECEIVER(this)) { + throw MakeTypeError(kCalledOnNonObject, PromiseRace); + } + + var deferred = NewPromiseCapability(this); try { for (var value of iterable) { - var reject = function(r) { deferred.reject(r) }; - this.resolve(value).then(function(x) { deferred.resolve(x) }, reject); - SET_PRIVATE(reject, promiseCombinedDeferredSymbol, deferred); + this.resolve(value).then(deferred.resolve, deferred.reject); + SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); } } catch (e) { deferred.reject(e) @@ -381,8 +437,6 @@ function PromiseHasUserDefinedRejectHandler() { DONT_ENUM | READ_ONLY); utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ - "defer", PromiseDeferred, - "accept", PromiseResolved, "reject", PromiseRejected, "all", PromiseAll, "race", PromiseRace, @@ -390,7 +444,6 @@ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ ]); utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ - "chain", PromiseChain, "then", PromiseThen, "catch", PromiseCatch ]); @@ -414,4 +467,14 @@ utils.InstallFunctions(extrasUtils, 0, [ "rejectPromise", PromiseReject ]); +// TODO(v8:4567): Allow experimental natives to remove function prototype +[PromiseChain, PromiseDeferred, PromiseResolved].forEach( + fn => %FunctionRemovePrototype(fn)); + +utils.Export(function(to) { + to.PromiseChain = PromiseChain; + to.PromiseDeferred = PromiseDeferred; + to.PromiseResolved = PromiseResolved; +}); + }) |