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