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