Chromium Code Reviews| Index: src/promise.js |
| diff --git a/src/promise.js b/src/promise.js |
| index d202f2886166af38985140786888831c0ba25be5..dc05637f854e81fb653feb47b4423a092b8bbbd9 100644 |
| --- a/src/promise.js |
| +++ b/src/promise.js |
| @@ -9,28 +9,14 @@ |
| // var $Object = global.Object |
| // var $WeakMap = global.WeakMap |
| +// For bootstrapper. |
| -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(); |
| - } |
| -} |
| - |
| - |
| -//------------------------------------------------------------------- |
| - |
| -// Core functionality. |
| +var IsPromise; |
| +var PromiseCreate; |
| +var PromiseResolve; |
| +var PromiseReject; |
| +var PromiseChain; |
| +var PromiseCatch; |
| // Status values: 0 = pending, +1 = resolved, -1 = rejected |
| var promiseStatus = GLOBAL_PRIVATE("Promise#status"); |
|
rossberg
2014/05/22 13:23:05
Can't these go into the closure as well?
Jakob Kummerow
2014/05/22 13:54:58
Unfortunately no, because debugger. Added a TODO.
|
| @@ -39,250 +25,274 @@ var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); |
| var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); |
| var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); |
| -function IsPromise(x) { |
| - return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); |
| -} |
| - |
| -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) { |
| - PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); |
| - PromiseSet(promise, status, value); |
| +(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 PromiseResolve(promise, x) { |
| - PromiseDone(promise, +1, x, promiseOnResolve) |
| -} |
| + // Core functionality. |
| -function PromiseReject(promise, r) { |
| - PromiseDone(promise, -1, r, promiseOnReject) |
| -} |
| + 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) { |
| + PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); |
| + PromiseSet(promise, status, value); |
| + } |
| + } |
| + |
| + 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 { |
| + %_CallFunction(x, deferred.resolve, deferred.reject, then); |
| + } catch(r) { |
| + deferred.reject(r); |
| + } |
| + return deferred.promise; |
| + } |
| + } |
| + return x; |
| + } |
| + 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 { |
| + %DebugPromiseHandlePrologue(function() { return deferred.promise }); |
| + deferred.reject(exception); |
| + } catch (e) { } finally { |
| + %DebugPromiseHandleEpilogue(); |
| + } |
| + } finally { |
| + %DebugPromiseHandleEpilogue(); |
| + } |
| + } |
| -// For API. |
| + function PromiseEnqueue(value, tasks) { |
| + %EnqueueMicrotask(function() { |
| + for (var i = 0; i < tasks.length; i += 2) { |
| + PromiseHandle(value, tasks[i], tasks[i + 1]) |
| + } |
| + }); |
| + } |
| -function PromiseNopResolver() {} |
| + function PromiseIdResolveHandler(x) { return x } |
| + function PromiseIdRejectHandler(r) { throw r } |
| -function PromiseCreate() { |
| - return new $Promise(PromiseNopResolver) |
| -} |
| + function PromiseNopResolver() {} |
| + // ------------------------------------------------------------------- |
| + // Define exported functions. |
| -// Convenience. |
| + // For bootstrapper. |
| -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) }); |
| + IsPromise = function IsPromise(x) { |
| + return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); |
| } |
| -} |
| - |
| -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) }); |
| + |
| + PromiseCreate = function PromiseCreate() { |
| + return new $Promise(PromiseNopResolver) |
| } |
| -} |
| - |
| - |
| -// 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; |
| + |
| + PromiseResolve = function PromiseResolve(promise, x) { |
| + PromiseDone(promise, +1, x, promiseOnResolve) |
| } |
| - return deferred.promise; |
| -} |
| -function PromiseCatch(onReject) { |
| - return this.then(UNDEFINED, onReject); |
| -} |
| + PromiseReject = function PromiseReject(promise, r) { |
| + PromiseDone(promise, -1, r, promiseOnReject) |
| + } |
| -function PromiseEnqueue(value, tasks) { |
| - %EnqueueMicrotask(function() { |
| - for (var i = 0; i < tasks.length; i += 2) { |
| - PromiseHandle(value, tasks[i], tasks[i + 1]) |
| + // 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 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 { |
| - %DebugPromiseHandlePrologue(function() { return deferred.promise }); |
| - deferred.reject(exception); |
| - } catch (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) }); |
| } |
| - } finally { |
| - %DebugPromiseHandleEpilogue(); |
| } |
| -} |
| - |
| - |
| -// Multi-unwrapped chaining with thenable coercion. |
| - |
| -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 |
| - ); |
| -} |
| - |
| -function PromiseCoerce(constructor, x) { |
| - if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { |
| - var then; |
| - try { |
| - then = x.then; |
| - } catch(r) { |
| - return %_CallFunction(constructor, r, PromiseRejected); |
| + |
| + 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) }); |
| } |
| - if (IS_SPEC_FUNCTION(then)) { |
| - var deferred = %_CallFunction(constructor, PromiseDeferred); |
| - try { |
| - %_CallFunction(x, deferred.resolve, deferred.reject, then); |
| - } catch(r) { |
| - deferred.reject(r); |
| - } |
| - return deferred.promise; |
| + } |
| + |
| + // 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; |
| } |
| - return x; |
| -} |
| + PromiseCatch = function PromiseCatch(onReject) { |
| + return this.then(UNDEFINED, onReject); |
| + } |
| -// Combinators. |
| + // Multi-unwrapped chaining with thenable coercion. |
| + |
| + 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 |
| + ); |
| + } |
| -function PromiseCast(x) { |
| - // TODO(rossberg): cannot do better until we support @@create. |
| - return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); |
| -} |
| + // Combinators. |
| -function PromiseAll(values) { |
| - var deferred = %_CallFunction(this, PromiseDeferred); |
| - var resolutions = []; |
| - if (!%_IsArray(values)) { |
| - deferred.reject(MakeTypeError('invalid_argument')); |
| + 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; |
| } |
| - try { |
| - var count = values.length; |
| - if (count === 0) { |
| - deferred.resolve(resolutions); |
| - } else { |
| + |
| + 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(i, x) { |
| - resolutions[i] = x; |
| - if (--count === 0) deferred.resolve(resolutions); |
| - }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once available |
| + function(x) { deferred.resolve(x) }, |
| function(r) { deferred.reject(r) } |
| ); |
| } |
| + } catch (e) { |
| + deferred.reject(e) |
| } |
| - } 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(); |
| %SetProperty(global, 'Promise', $Promise, DONT_ENUM); |
| InstallFunctions($Promise, DONT_ENUM, [ |
| @@ -298,6 +308,5 @@ function SetUpPromise() { |
| "then", PromiseThen, |
| "catch", PromiseCatch |
| ]); |
| -} |
| -SetUpPromise(); |
| +})(); |