| Index: src/promise.js
|
| diff --git a/src/promise.js b/src/promise.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..23801801cd6d3c6726d1661b64592b03dd841100
|
| --- /dev/null
|
| +++ b/src/promise.js
|
| @@ -0,0 +1,271 @@
|
| +// 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;
|
| +
|
| +
|
| +// TODO(rossberg): A temporary shim for creating microtasks.
|
| +function Task(task) {
|
| + var dummy = {}
|
| + $Object.observe(dummy, task)
|
| + dummy.dummy = dummy
|
| +}
|
| +
|
| +
|
| +//-------------------------------------------------------------------
|
| +
|
| +// Core functionality.
|
| +
|
| +// 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");
|
| +
|
| +function IsPromise(x) {
|
| + return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus);
|
| +}
|
| +
|
| +function Promise(resolver) {
|
| + SET_PRIVATE(this, promiseStatus, 0);
|
| + SET_PRIVATE(this, promiseOnResolve, []);
|
| + SET_PRIVATE(this, promiseOnReject, []);
|
| + var that = this;
|
| + resolver(function(x) { PromiseResolve(that, x) },
|
| + function(r) { PromiseReject(that, r) });
|
| +}
|
| +
|
| +function PromiseResolve(promise, x) {
|
| + if (GET_PRIVATE(promise, promiseStatus) !== 0)
|
| + throw MakeTypeError('promise_not_pending', [promise]);
|
| + PromiseQueue(GET_PRIVATE(promise, promiseOnResolve), x);
|
| + SET_PRIVATE(promise, promiseValue, x);
|
| + SET_PRIVATE(promise, promiseOnResolve, UNDEFINED);
|
| + SET_PRIVATE(promise, promiseOnReject, UNDEFINED);
|
| + SET_PRIVATE(promise, promiseStatus, +1);
|
| +}
|
| +
|
| +function PromiseReject(promise, r) {
|
| + if (GET_PRIVATE(promise, promiseStatus) !== 0)
|
| + throw MakeTypeError('promise_not_pending', [promise]);
|
| + PromiseQueue(GET_PRIVATE(promise, promiseOnReject), r);
|
| + SET_PRIVATE(promise, promiseValue, r);
|
| + SET_PRIVATE(promise, promiseOnResolve, UNDEFINED);
|
| + SET_PRIVATE(promise, promiseOnReject, UNDEFINED);
|
| + SET_PRIVATE(promise, promiseStatus, -1);
|
| +}
|
| +
|
| +function PromiseQueue(tasks, x) {
|
| + for (var i in tasks) {
|
| + Task(function() { tasks[i](x) });
|
| + }
|
| +}
|
| +
|
| +
|
| +// Convenience.
|
| +
|
| +function PromiseDeferred() {
|
| + var result = {};
|
| + result.promise = new this(function(resolve, reject) {
|
| + result.resolve = resolve;
|
| + result.reject = reject;
|
| + })
|
| + return result;
|
| +}
|
| +
|
| +function PromiseResolved(x) {
|
| + return new this(function(resolve, reject) { resolve(x) });
|
| +}
|
| +
|
| +function PromiseRejected(r) {
|
| + 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 = Promise.deferred.call(this.constructor);
|
| + switch (GET_PRIVATE(this, promiseStatus)) {
|
| + case UNDEFINED:
|
| + throw MakeTypeError('not_a_promise', [this]);
|
| + case 0: // Pending
|
| + GET_PRIVATE(this, promiseOnResolve).push(
|
| + PromiseChain(deferred, onResolve));
|
| + GET_PRIVATE(this, promiseOnReject).push(
|
| + PromiseChain(deferred, onReject));
|
| + break
|
| + case +1: // Resolved
|
| + PromiseQueue([PromiseChain(deferred, onResolve)],
|
| + GET_PRIVATE(this, promiseValue));
|
| + break
|
| + case -1: // Rejected
|
| + PromiseQueue([PromiseChain(deferred, onReject)],
|
| + GET_PRIVATE(this, promiseValue));
|
| + break
|
| + default:
|
| + unreachable();
|
| + }
|
| + return deferred.promise;
|
| +}
|
| +
|
| +function PromiseCatch(onReject) {
|
| + return this.when(UNDEFINED, onReject);
|
| +}
|
| +
|
| +// TODO(rossberg): uncurry
|
| +function PromiseChain(deferred, handler) {
|
| + return function(x) {
|
| + try {
|
| + var y = handler(x);
|
| + if (y === deferred.promise)
|
| + throw MakeTypeError('promise_cyclic', [y]);
|
| + else if (IsPromise(y))
|
| + y.when(deferred.resolve, deferred.reject);
|
| + else
|
| + deferred.resolve(y);
|
| + } catch(e) {
|
| + deferred.reject(e);
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +// 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(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) {
|
| + if (x instanceof this) return x
|
| + if (IsPromise(x)) {
|
| + var result = this.deferred()
|
| + x.when(result.resolve, result.reject)
|
| + return result.promise
|
| + }
|
| + return this.resolved(x)
|
| +}
|
| +
|
| +function PromiseAll(values) {
|
| + var deferred = this.deferred()
|
| + var count = 0
|
| + for (var i in values) {
|
| + ++count
|
| + this.cast(values[i]).when(
|
| + function(x) { if (--count === 0) deferred.resolve(undefined) },
|
| + 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 in values) {
|
| + 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();
|
|
|