| Index: src/promise.js
|
| diff --git a/src/promise.js b/src/promise.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cbd7836907b5d01b1766706fd057938fcf486733
|
| --- /dev/null
|
| +++ b/src/promise.js
|
| @@ -0,0 +1,292 @@
|
| +// 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) {
|
| + 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) {
|
| + 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,
|
| + "cast", PromiseCast
|
| + ]);
|
| + InstallFunctions($Promise.prototype, DONT_ENUM, [
|
| + "when", PromiseWhen,
|
| + "then", PromiseThen,
|
| + "catch", PromiseCatch
|
| + ]);
|
| +}
|
| +
|
| +SetUpPromise();
|
|
|