| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 'use strict'; | |
| 6 | |
| 7 /** @suppress {duplicate} */ | |
| 8 var base = base || {}; | |
| 9 | |
| 10 (function() { | |
| 11 /** | |
| 12 * A wrapper around a Promise object that keeps track of all | |
| 13 * outstanding promises. This function is written to serve as a | |
| 14 * drop-in replacement for the native Promise constructor. To create | |
| 15 * a SpyPromise from an existing native Promise, use | |
| 16 * SpyPromise.resolve. | |
| 17 * | |
| 18 * Note that this is a pseudo-constructor that actually returns a | |
| 19 * regular promise with appropriate handlers attached. This detail | |
| 20 * should be transparent when SpyPromise.activate has been called. | |
| 21 * | |
| 22 * The normal way to use this class is within a call to | |
| 23 * SpyPromise.run, for example: | |
| 24 * | |
| 25 * base.SpyPromise.run(function() { | |
| 26 * myCodeThatUsesPromises(); | |
| 27 * }); | |
| 28 * base.SpyPromise.settleAll().then(function() { | |
| 29 * console.log('All promises have been settled!'); | |
| 30 * }); | |
| 31 * | |
| 32 * @constructor | |
| 33 * @extends {Promise} | |
| 34 * @param {function(function(?):?, function(*):?):?} func A function | |
| 35 * of the same type used as an argument to the native Promise | |
| 36 * constructor, in other words, a function which is called | |
| 37 * immediately, and whose arguments are a resolve function and a | |
| 38 * reject function. | |
| 39 */ | |
| 40 base.SpyPromise = function(func) { | |
| 41 var unsettled = new RealPromise(func); | |
| 42 var unsettledId = remember(unsettled); | |
| 43 return unsettled.then(function(/** * */value) { | |
| 44 forget(unsettledId); | |
| 45 return value; | |
| 46 }, function(error) { | |
| 47 forget(unsettledId); | |
| 48 throw error; | |
| 49 }); | |
| 50 }; | |
| 51 | |
| 52 /** | |
| 53 * The real promise constructor. Needed because it is normally hidden | |
| 54 * by SpyPromise.activate or SpyPromise.run. | |
| 55 * @const | |
| 56 */ | |
| 57 var RealPromise = Promise; | |
| 58 | |
| 59 /** | |
| 60 * The real window.setTimeout method. Needed because some test | |
| 61 * frameworks like to replace this method with a fake implementation. | |
| 62 * @const | |
| 63 */ | |
| 64 var realSetTimeout = window.setTimeout.bind(window); | |
| 65 | |
| 66 /** | |
| 67 * The number of unsettled promises. | |
| 68 * @type {number} | |
| 69 */ | |
| 70 base.SpyPromise.unsettledCount; // initialized by reset() | |
| 71 | |
| 72 /** | |
| 73 * A collection of all unsettled promises. | |
| 74 * @type {!Object<number,!Promise>} | |
| 75 */ | |
| 76 var unsettled; // initialized by reset() | |
| 77 | |
| 78 /** | |
| 79 * A counter used to assign ID numbers to new SpyPromise objects. | |
| 80 * @type {number} | |
| 81 */ | |
| 82 var nextPromiseId; // initialized by reset() | |
| 83 | |
| 84 /** | |
| 85 * A promise returned by SpyPromise.settleAll. | |
| 86 * @type {Promise<null>} | |
| 87 */ | |
| 88 var settleAllPromise; // initialized by reset() | |
| 89 | |
| 90 /** | |
| 91 * Records an unsettled promise. | |
| 92 * | |
| 93 * @param {!Promise} unsettledPromise | |
| 94 * @return {number} The ID number to be passed to forget_. | |
| 95 */ | |
| 96 function remember(unsettledPromise) { | |
| 97 var id = nextPromiseId++; | |
| 98 if (unsettled[id] != null) { | |
| 99 throw Error('Duplicate ID: ' + id); | |
| 100 } | |
| 101 base.SpyPromise.unsettledCount++; | |
| 102 unsettled[id] = unsettledPromise; | |
| 103 return id; | |
| 104 }; | |
| 105 | |
| 106 /** | |
| 107 * Forgets a promise. Called after the promise has been settled. | |
| 108 * | |
| 109 * @param {number} id | |
| 110 * @private | |
| 111 */ | |
| 112 function forget(id) { | |
| 113 base.debug.assert(unsettled[id] != null); | |
| 114 base.SpyPromise.unsettledCount--; | |
| 115 delete unsettled[id]; | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Forgets about all unsettled promises. | |
| 120 */ | |
| 121 base.SpyPromise.reset = function() { | |
| 122 base.SpyPromise.unsettledCount = 0; | |
| 123 unsettled = {}; | |
| 124 nextPromiseId = 0; | |
| 125 settleAllPromise = null; | |
| 126 }; | |
| 127 | |
| 128 // Initialize static variables. | |
| 129 base.SpyPromise.reset(); | |
| 130 | |
| 131 /** | |
| 132 * Tries to wait until all promises has been settled. | |
| 133 * | |
| 134 * @param {number=} opt_maxTimeMs The maximum number of milliseconds | |
| 135 * (approximately) to wait (default: 1000). | |
| 136 * @return {!Promise<null>} A real promise that is resolved when all | |
| 137 * SpyPromises have been settled, or rejected after opt_maxTimeMs | |
| 138 * milliseconds have elapsed. | |
| 139 */ | |
| 140 base.SpyPromise.settleAll = function(opt_maxTimeMs) { | |
| 141 if (settleAllPromise) { | |
| 142 return settleAllPromise; | |
| 143 } | |
| 144 | |
| 145 var maxDelay = opt_maxTimeMs == null ? 1000 : opt_maxTimeMs; | |
| 146 | |
| 147 /** | |
| 148 * @param {number} count | |
| 149 * @param {number} totalDelay | |
| 150 * @return {!Promise<null>} | |
| 151 */ | |
| 152 function loop(count, totalDelay) { | |
| 153 return new RealPromise(function(resolve, reject) { | |
| 154 if (base.SpyPromise.unsettledCount == 0) { | |
| 155 settleAllPromise = null; | |
| 156 resolve(null); | |
| 157 } else if (totalDelay > maxDelay) { | |
| 158 settleAllPromise = null; | |
| 159 base.SpyPromise.reset(); | |
| 160 reject(new Error('base.SpyPromise.settleAll timed out')); | |
| 161 } else { | |
| 162 // This implements quadratic backoff according to Euler's | |
| 163 // triangular number formula. | |
| 164 var delay = count; | |
| 165 | |
| 166 // Must jump through crazy hoops to get a real timer in a unit test. | |
| 167 realSetTimeout(function() { | |
| 168 resolve(loop( | |
| 169 count + 1, | |
| 170 delay + totalDelay)); | |
| 171 }, delay); | |
| 172 } | |
| 173 }); | |
| 174 }; | |
| 175 | |
| 176 // An extra promise needed here to prevent the loop function from | |
| 177 // finishing before settleAllPromise is set. If that happens, | |
| 178 // settleAllPromise will never be reset to null. | |
| 179 settleAllPromise = RealPromise.resolve().then(function() { | |
| 180 return loop(0, 0); | |
| 181 }); | |
| 182 return settleAllPromise; | |
| 183 }; | |
| 184 | |
| 185 /** | |
| 186 * Only for testing this class. Do not use. | |
| 187 * @returns {boolean} True if settleAll is executing. | |
| 188 */ | |
| 189 base.SpyPromise.isSettleAllRunning = function() { | |
| 190 return settleAllPromise != null; | |
| 191 }; | |
| 192 | |
| 193 /** | |
| 194 * Wrapper for Promise.resolve. | |
| 195 * | |
| 196 * @param {*} value | |
| 197 * @return {!base.SpyPromise} | |
| 198 */ | |
| 199 base.SpyPromise.resolve = function(value) { | |
| 200 return new base.SpyPromise(function(resolve, reject) { | |
| 201 resolve(value); | |
| 202 }); | |
| 203 }; | |
| 204 | |
| 205 /** | |
| 206 * Wrapper for Promise.reject. | |
| 207 * | |
| 208 * @param {*} value | |
| 209 * @return {!base.SpyPromise} | |
| 210 */ | |
| 211 base.SpyPromise.reject = function(value) { | |
| 212 return new base.SpyPromise(function(resolve, reject) { | |
| 213 reject(value); | |
| 214 }); | |
| 215 }; | |
| 216 | |
| 217 /** | |
| 218 * Wrapper for Promise.all. | |
| 219 * | |
| 220 * @param {!Array<Promise>} promises | |
| 221 * @return {!base.SpyPromise} | |
| 222 */ | |
| 223 base.SpyPromise.all = function(promises) { | |
| 224 return base.SpyPromise.resolve(RealPromise.all(promises)); | |
| 225 }; | |
| 226 | |
| 227 /** | |
| 228 * Wrapper for Promise.race. | |
| 229 * | |
| 230 * @param {!Array<Promise>} promises | |
| 231 * @return {!base.SpyPromise} | |
| 232 */ | |
| 233 base.SpyPromise.race = function(promises) { | |
| 234 return base.SpyPromise.resolve(RealPromise.race(promises)); | |
| 235 }; | |
| 236 | |
| 237 /** | |
| 238 * Sets Promise = base.SpyPromise. Must not be called more than once | |
| 239 * without an intervening call to restore(). | |
| 240 */ | |
| 241 base.SpyPromise.activate = function() { | |
| 242 if (settleAllPromise) { | |
| 243 throw Error('called base.SpyPromise.activate while settleAll is running'); | |
| 244 } | |
| 245 if (Promise === base.SpyPromise) { | |
| 246 throw Error('base.SpyPromise is already active'); | |
| 247 } | |
| 248 Promise = base.SpyPromise; | |
| 249 }; | |
| 250 | |
| 251 /** | |
| 252 * Restores the original value of Promise. | |
| 253 */ | |
| 254 base.SpyPromise.restore = function() { | |
| 255 if (settleAllPromise) { | |
| 256 throw Error('called base.SpyPromise.restore while settleAll is running'); | |
| 257 } | |
| 258 if (Promise === base.SpyPromise) { | |
| 259 Promise = RealPromise; | |
| 260 } else if (Promise === RealPromise) { | |
| 261 throw new Error('base.SpyPromise is not active.'); | |
| 262 } else { | |
| 263 throw new Error('Something fishy is going on.'); | |
| 264 } | |
| 265 }; | |
| 266 | |
| 267 /** | |
| 268 * Calls func with Promise equal to base.SpyPromise. | |
| 269 * | |
| 270 * @param {function():void} func A function which is expected to | |
| 271 * create one or more promises. | |
| 272 * @param {number=} opt_timeoutMs An optional timeout specifying how | |
| 273 * long to wait for promise chains started in func to be settled. | |
| 274 * (default: 1000 ms) | |
| 275 * @return {!Promise<null>} A promise that is resolved after every | |
| 276 * promise chain started in func is fully settled, or rejected | |
| 277 * after a opt_timeoutMs. In any case, the original value of the | |
| 278 * Promise constructor is restored before this promise is settled. | |
| 279 */ | |
| 280 base.SpyPromise.run = function(func, opt_timeoutMs) { | |
| 281 base.SpyPromise.activate(); | |
| 282 try { | |
| 283 func(); | |
| 284 } finally { | |
| 285 return base.SpyPromise.settleAll(opt_timeoutMs).then(function() { | |
| 286 base.SpyPromise.restore(); | |
| 287 return null; | |
| 288 }, function(error) { | |
| 289 base.SpyPromise.restore(); | |
| 290 throw error; | |
| 291 }); | |
| 292 } | |
| 293 }; | |
| 294 })(); | |
| OLD | NEW |