Jamie Gennis | 0105835 | 2012-05-06 12:48:05 -0700 | [diff] [blame] | 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 | }); |