// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview This implementes a future promise class. */ cr.define('cr', function() { /** * Sentinel used to mark a value as pending. * @const */ var PENDING_VALUE = {}; /** * Creates a future promise. * @param {*=} opt_value The value to set the promise to. If set completes * the promise immediately. * @constructor */ function Promise(opt_value) { /** * An array of the callbacks. * @type {!Array.<!Function>} * @private */ this.callbacks_ = []; if (arguments.length > 0) this.value = opt_value; } Promise.prototype = { /** * The current value. * @type {*} * @private */ value_: PENDING_VALUE, /** * The value of the future promise. Accessing this before the promise has * been fulfilled will throw an error. If this is set to an exception * accessing this will throw as well. * @type {*} */ get value() { return this.done ? this.value_ : undefined; }, set value(value) { if (!this.done) { this.value_ = value; for (var i = 0; i < this.callbacks_.length; i++) { this.callbacks_[i].call(null, value); } this.callbacks_.length = 0; } }, /** * Whether the future promise has been fulfilled. * @type {boolean} */ get done() { return this.value_ !== PENDING_VALUE; }, /** * Adds a listener to the future promise. The function will be called when * the promise is fulfilled. If the promise is already fullfilled this will * never call the function. * @param {!Function} fun The function to call. */ addListener: function(fun) { if (this.done) fun(this.value); else this.callbacks_.push(fun); }, /** * Removes a previously added listener from the future promise. * @param {!Function} fun The function to remove. */ removeListener: function(fun) { var i = this.callbacks_.indexOf(fun); if (i >= 0) this.callbacks_.splice(i, 1); }, /** * If the promise is done then this returns the string representation of * the value. * @return {string} The string representation of the promise. * @override */ toString: function() { if (this.done) return String(this.value); else return '[object Promise]'; }, /** * Override to allow arithmetic. * @override */ valueOf: function() { return this.value; } }; /** * When a future promise is done call {@code fun}. This also calls the * function if the promise has already been fulfilled. * @param {!Promise} p The promise. * @param {!Function} fun The function to call when the promise is fulfilled. */ Promise.when = function(p, fun) { p.addListener(fun); }; /** * Creates a new promise the will be fulfilled after {@code t} ms. * @param {number} t The time to wait before the promise is fulfilled. * @param {*=} opt_value The value to return after the wait. * @return {!Promise} The new future promise. */ Promise.wait = function(t, opt_value) { var p = new Promise; window.setTimeout(function() { p.value = opt_value; }, t); return p; }; /** * Creates a new future promise that is fulfilled when any of the promises are * fulfilled. The value of the returned promise will be the value of the first * fulfilled promise. * @param {...!Promise} var_args The promises used to build up the new * promise. * @return {!Promise} The new promise that will be fulfilled when any of the * passed in promises are fulfilled. */ Promise.any = function(var_args) { var p = new Promise; function f(v) { p.value = v; } for (var i = 0; i < arguments.length; i++) { arguments[i].addListener(f); } return p; }; /** * Creates a new future promise that is fulfilled when all of the promises are * fulfilled. The value of the returned promise is an array of the values of * the promises passed in. * @param {...!Promise} var_args The promises used to build up the new * promise. * @return {!Promise} The promise that wraps all the promises in the array. */ Promise.all = function(var_args) { var p = new Promise; var args = Array.prototype.slice.call(arguments); var count = args.length; if (!count) { p.value = []; return p; } function f(v) { count--; if (!count) { p.value = args.map(function(argP) { return argP.value; }); } } // Do not use count here since count may be decremented in the call to // addListener if the promise is already done. for (var i = 0; i < args.length; i++) { args[i].addListener(f); } return p; }; /** * Wraps an event in a future promise. * @param {!EventTarget} target The object that dispatches the event. * @param {string} type The type of the event. * @param {boolean=} opt_useCapture Whether to listen to the capture phase or * the bubble phase. * @return {!Promise} The promise that will be fulfilled when the event is * dispatched. */ Promise.event = function(target, type, opt_useCapture) { var p = new Promise; target.addEventListener(type, function(e) { p.value = e; }, opt_useCapture); return p; }; return { Promise: Promise }; });