Home | History | Annotate | Download | only in cr
      1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview This implementes a future promise class.
      7  */
      8 
      9 cr.define('cr', function() {
     10 
     11   /**
     12    * Sentinel used to mark a value as pending.
     13    */
     14   const PENDING_VALUE = {};
     15 
     16   /**
     17    * Creates a future promise.
     18    * @param {*=} opt_value The value to set the promise to. If set completes
     19    *     the promise immediately.
     20    * @constructor
     21    */
     22   function Promise(opt_value) {
     23     /**
     24      * An array of the callbacks.
     25      * @type {!Array.<!Function>}
     26      * @private
     27      */
     28     this.callbacks_ = [];
     29 
     30     if (arguments.length > 0)
     31       this.value = opt_value;
     32   }
     33 
     34   Promise.prototype = {
     35     /**
     36      * The current value.
     37      * @type {*}
     38      * @private
     39      */
     40     value_: PENDING_VALUE,
     41 
     42     /**
     43      * The value of the future promise. Accessing this before the promise has
     44      * been fulfilled will throw an error. If this is set to an exception
     45      * accessing this will throw as well.
     46      * @type {*}
     47      */
     48     get value() {
     49       return this.done ? this.value_ : undefined;
     50     },
     51     set value(value) {
     52       if (!this.done) {
     53         this.value_ = value;
     54         for (var i = 0; i < this.callbacks_.length; i++) {
     55           this.callbacks_[i].call(null, value);
     56         }
     57         this.callbacks_.length = 0;
     58       }
     59     },
     60 
     61     /**
     62      * Whether the future promise has been fulfilled.
     63      * @type {boolean}
     64      */
     65     get done() {
     66       return this.value_ !== PENDING_VALUE;
     67     },
     68 
     69     /**
     70      * Adds a listener to the future promise. The function will be called when
     71      * the promise is fulfilled. If the promise is already fullfilled this will
     72      * never call the function.
     73      * @param {!Function} fun The function to call.
     74      */
     75     addListener: function(fun) {
     76       if (this.done)
     77         fun(this.value);
     78       else
     79         this.callbacks_.push(fun);
     80     },
     81 
     82     /**
     83      * Removes a previously added listener from the future promise.
     84      * @param {!Function} fun The function to remove.
     85      */
     86     removeListener: function(fun) {
     87       var i = this.callbacks_.indexOf(fun);
     88       if (i >= 0)
     89         this.callbacks_.splice(i, 1);
     90     },
     91 
     92     /**
     93      * If the promise is done then this returns the string representation of
     94      * the value.
     95      * @return {string} The string representation of the promise.
     96      * @override
     97      */
     98     toString: function() {
     99       if (this.done)
    100         return String(this.value);
    101       else
    102         return '[object Promise]';
    103     },
    104 
    105     /**
    106      * Override to allow arithmetic.
    107      * @override
    108      */
    109     valueOf: function() {
    110       return this.value;
    111     }
    112   };
    113 
    114   /**
    115    * When a future promise is done call {@code fun}. This also calls the
    116    * function if the promise has already been fulfilled.
    117    * @param {!Promise} p The promise.
    118    * @param {!Function} fun The function to call when the promise is fulfilled.
    119    */
    120   Promise.when = function(p, fun) {
    121     p.addListener(fun);
    122   };
    123 
    124   /**
    125    * Creates a new promise the will be fulfilled after {@code t} ms.
    126    * @param {number} t The time to wait before the promise is fulfilled.
    127    * @param {*=} opt_value The value to return after the wait.
    128    * @return {!Promise} The new future promise.
    129    */
    130   Promise.wait = function(t, opt_value) {
    131     var p = new Promise;
    132     window.setTimeout(function() {
    133       p.value = opt_value;
    134     }, t);
    135     return p;
    136   };
    137 
    138   /**
    139    * Creates a new future promise that is fulfilled when any of the promises are
    140    * fulfilled. The value of the returned promise will be the value of the first
    141    * fulfilled promise.
    142    * @param {...!Promise} var_args The promises used to build up the new
    143    *     promise.
    144    * @return {!Promise} The new promise that will be fulfilled when any of the
    145    *     passed in promises are fulfilled.
    146    */
    147   Promise.any = function(var_args) {
    148     var p = new Promise;
    149     function f(v) {
    150       p.value = v;
    151     }
    152     for (var i = 0; i < arguments.length; i++) {
    153       arguments[i].addListener(f);
    154     }
    155     return p;
    156   };
    157 
    158   /**
    159    * Creates a new future promise that is fulfilled when all of the promises are
    160    * fulfilled. The value of the returned promise is an array of the values of
    161    * the promises passed in.
    162    * @param {...!Promise} var_args The promises used to build up the new
    163    *     promise.
    164    * @return {!Promise} The promise that wraps all the promises in the array.
    165    */
    166   Promise.all = function(var_args) {
    167     var p = new Promise;
    168     var args = Array.prototype.slice.call(arguments);
    169     var count = args.length;
    170     if (!count) {
    171       p.value = [];
    172       return p;
    173     }
    174 
    175     function f(v) {
    176       count--;
    177       if (!count) {
    178         p.value = args.map(function(argP) {
    179           return argP.value;
    180         });
    181       }
    182     }
    183 
    184     // Do not use count here since count may be decremented in the call to
    185     // addListener if the promise is already done.
    186     for (var i = 0; i < args.length; i++) {
    187       args[i].addListener(f);
    188     }
    189 
    190     return p;
    191   };
    192 
    193   /**
    194    * Wraps an event in a future promise.
    195    * @param {!EventTarget} target The object that dispatches the event.
    196    * @param {string} type The type of the event.
    197    * @param {boolean=} opt_useCapture Whether to listen to the capture phase or
    198    *     the bubble phase.
    199    * @return {!Promise} The promise that will be fulfilled when the event is
    200    *     dispatched.
    201    */
    202   Promise.event = function(target, type, opt_useCapture) {
    203     var p = new Promise;
    204     target.addEventListener(type, function(e) {
    205       p.value = e;
    206     }, opt_useCapture);
    207     return p;
    208   };
    209 
    210   return {
    211     Promise: Promise
    212   };
    213 });
    214