Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | |
| 2 // Redistribution and use in source and binary forms, with or without | |
| 3 // modification, are permitted provided that the following conditions are | |
| 4 // met: | |
| 5 // | |
| 6 // * Redistributions of source code must retain the above copyright | |
| 7 // notice, this list of conditions and the following disclaimer. | |
| 8 // * Redistributions in binary form must reproduce the above | |
| 9 // copyright notice, this list of conditions and the following | |
| 10 // disclaimer in the documentation and/or other materials provided | |
| 11 // with the distribution. | |
| 12 // * Neither the name of Google Inc. nor the names of its | |
| 13 // contributors may be used to endorse or promote products derived | |
| 14 // from this software without specific prior written permission. | |
| 15 // | |
| 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | |
| 28 | |
| 29 "use strict"; | |
| 30 | |
| 31 // This file relies on the fact that the following declaration has been made | |
| 32 // in runtime.js: | |
| 33 // var $Object = global.Object | |
| 34 // var $WeakMap = global.WeakMap | |
| 35 | |
| 36 | |
| 37 var $Promise = Promise; | |
| 38 | |
| 39 | |
| 40 //------------------------------------------------------------------- | |
| 41 | |
| 42 // Core functionality. | |
| 43 | |
| 44 // Event queue format: [(value, [(handler, deferred)*])*] | |
| 45 // I.e., a list of value/tasks pairs, where the value is a resolution value or | |
| 46 // rejection reason, and the tasks are a respective list of handler/deferred | |
| 47 // pairs waiting for notification of this value. Each handler is an onResolve or | |
| 48 // onReject function provided to the same call of 'when' that produced the | |
| 49 // associated deferred. | |
| 50 var promiseEvents = new InternalArray; | |
| 51 | |
| 52 // Status values: 0 = pending, +1 = resolved, -1 = rejected | |
| 53 var promiseStatus = NEW_PRIVATE("Promise#status"); | |
| 54 var promiseValue = NEW_PRIVATE("Promise#value"); | |
| 55 var promiseOnResolve = NEW_PRIVATE("Promise#onResolve"); | |
| 56 var promiseOnReject = NEW_PRIVATE("Promise#onReject"); | |
| 57 var promiseRaw = NEW_PRIVATE("Promise#raw"); | |
| 58 | |
| 59 function IsPromise(x) { | |
| 60 return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); | |
| 61 } | |
| 62 | |
| 63 function Promise(resolver) { | |
| 64 if (resolver === promiseRaw) return; | |
| 65 var promise = PromiseInit(this); | |
| 66 resolver(function(x) { PromiseResolve(promise, x) }, | |
| 67 function(r) { PromiseReject(promise, r) }); | |
| 68 } | |
| 69 | |
| 70 function PromiseSet(promise, status, value, onResolve, onReject) { | |
| 71 SET_PRIVATE(promise, promiseStatus, status); | |
| 72 SET_PRIVATE(promise, promiseValue, value); | |
| 73 SET_PRIVATE(promise, promiseOnResolve, onResolve); | |
| 74 SET_PRIVATE(promise, promiseOnReject, onReject); | |
| 75 return promise; | |
| 76 } | |
| 77 | |
| 78 function PromiseInit(promise) { | |
| 79 return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray) | |
| 80 } | |
| 81 | |
| 82 function PromiseDone(promise, status, value, promiseQueue) { | |
| 83 if (GET_PRIVATE(promise, promiseStatus) !== 0) | |
| 84 throw MakeTypeError('promise_not_pending', [promise]); | |
| 85 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); | |
| 86 PromiseSet(promise, status, value); | |
| 87 } | |
| 88 | |
| 89 function PromiseResolve(promise, x) { | |
| 90 PromiseDone(promise, +1, x, promiseOnResolve) | |
| 91 } | |
| 92 | |
| 93 function PromiseReject(promise, r) { | |
| 94 PromiseDone(promise, -1, r, promiseOnReject) | |
| 95 } | |
| 96 | |
| 97 | |
| 98 // Convenience. | |
| 99 | |
| 100 function PromiseDeferred() { | |
| 101 if (this === $Promise) { | |
| 102 // Optimized case, avoid extra closure. | |
| 103 var promise = PromiseInit(new Promise(promiseRaw)); | |
| 104 return { | |
| 105 promise: promise, | |
| 106 resolve: function(x) { PromiseResolve(promise, x) }, | |
| 107 reject: function(r) { PromiseReject(promise, r) } | |
| 108 }; | |
| 109 } else { | |
| 110 var result = {}; | |
| 111 result.promise = new this(function(resolve, reject) { | |
| 112 result.resolve = resolve; | |
| 113 result.reject = reject; | |
| 114 }) | |
| 115 return result; | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 function PromiseResolved(x) { | |
| 120 if (this === $Promise) { | |
| 121 // Optimized case, avoid extra closure. | |
| 122 return PromiseSet(new Promise(promiseRaw), +1, x); | |
| 123 } else { | |
| 124 return new this(function(resolve, reject) { resolve(x) }); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 function PromiseRejected(r) { | |
| 129 if (this === $Promise) { | |
| 130 // Optimized case, avoid extra closure. | |
| 131 return PromiseSet(new Promise(promiseRaw), -1, r); | |
| 132 } else { | |
| 133 return new this(function(resolve, reject) { reject(r) }); | |
| 134 } | |
| 135 } | |
| 136 | |
| 137 | |
| 138 // Simple chaining (a.k.a. flatMap). | |
| 139 | |
| 140 function PromiseNopHandler() {} | |
| 141 | |
| 142 function PromiseWhen(onResolve, onReject) { | |
| 143 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; | |
| 144 onReject = IS_UNDEFINED(onReject) ? PromiseNopHandler : onReject; | |
| 145 var deferred = %_CallFunction(this.constructor, PromiseDeferred); | |
| 146 switch (GET_PRIVATE(this, promiseStatus)) { | |
| 147 case UNDEFINED: | |
| 148 throw MakeTypeError('not_a_promise', [this]); | |
| 149 case 0: // Pending | |
| 150 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); | |
| 151 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); | |
| 152 break; | |
| 153 case +1: // Resolved | |
| 154 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]); | |
| 155 break; | |
| 156 case -1: // Rejected | |
| 157 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]); | |
| 158 break; | |
| 159 } | |
| 160 return deferred.promise; | |
| 161 } | |
| 162 | |
| 163 function PromiseCatch(onReject) { | |
| 164 return this.when(UNDEFINED, onReject); | |
| 165 } | |
| 166 | |
| 167 function PromiseEnqueue(value, tasks) { | |
| 168 promiseEvents.push(value, tasks); | |
| 169 %SetMicrotasksPending(true); | |
| 170 } | |
| 171 | |
| 172 function PromiseMicrotasksRunner() { | |
| 173 var events = promiseEvents; | |
| 174 if (events.length > 0) { | |
| 175 promiseEvents = new InternalArray; | |
| 176 for (var i = 0; i < events.length; i += 2) { | |
| 177 var value = events[i]; | |
| 178 var tasks = events[i + 1]; | |
| 179 for (var j = 0; j < tasks.length; j += 2) { | |
| 180 var handler = tasks[j]; | |
| 181 var deferred = tasks[j + 1]; | |
| 182 try { | |
| 183 var result = handler(value); | |
| 184 if (result === deferred.promise) | |
| 185 throw MakeTypeError('promise_cyclic', [result]); | |
| 186 else if (IsPromise(result)) | |
| 187 result.when(deferred.resolve, deferred.reject); | |
| 188 else | |
| 189 deferred.resolve(result); | |
| 190 } catch(e) { | |
| 191 // TODO(rossberg): perhaps log uncaught exceptions below. | |
| 192 try { deferred.reject(e) } catch(e) {} | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 } | |
| 197 } | |
| 198 RunMicrotasks.runners.push(PromiseMicrotasksRunner); | |
| 199 | |
| 200 | |
| 201 // Extended functionality for multi-unwrapping chaining and coercive 'then'. | |
| 202 | |
| 203 function PromiseThen(onResolve, onReject) { | |
| 204 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; | |
| 205 var that = this; | |
| 206 var constructor = this.constructor; | |
| 207 return this.when( | |
| 208 function(x) { | |
| 209 x = PromiseCoerce(constructor, x); | |
| 210 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : | |
| 211 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x); | |
| 212 }, | |
| 213 onReject | |
| 214 ); | |
| 215 } | |
| 216 | |
| 217 PromiseCoerce.table = new $WeakMap; | |
| 218 | |
| 219 function PromiseCoerce(constructor, x) { | |
| 220 if (IsPromise(x)) { | |
| 221 return x; | |
| 222 } else if (!IS_NULL_OR_UNDEFINED(x) && 'then' in TO_OBJECT_INLINE(x)) { | |
| 223 if (PromiseCoerce.table.has(x)) { | |
| 224 return PromiseCoerce.table.get(x); | |
| 225 } else { | |
| 226 var deferred = constructor.deferred(); | |
| 227 PromiseCoerce.table.set(x, deferred.promise); | |
| 228 try { | |
| 229 x.then(deferred.resolve, deferred.reject); | |
| 230 } catch(e) { | |
| 231 deferred.reject(e); | |
| 232 } | |
| 233 return deferred.promise; | |
| 234 } | |
| 235 } else { | |
| 236 return x; | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 | |
| 241 // Combinators. | |
| 242 | |
| 243 function PromiseCast(x) { | |
| 244 // TODO(rossberg): cannot do better until we support @@create. | |
| 245 return IsPromise(x) ? x : this.resolved(x); | |
| 246 } | |
| 247 | |
| 248 function PromiseAll(values) { | |
|
yhirano
2013/11/18 03:59:04
This function returns a promise which will never b
rossberg
2013/11/18 10:56:05
Oops, I somehow managed to misread the spec 8-}, a
yhirano
2013/11/18 11:03:46
Sorry, my comment was wrong.
I wanted to say:
Thi
rossberg
2013/11/18 11:51:09
Ah, excellent point. Fixed and test case added (al
| |
| 249 var deferred = this.deferred(); | |
| 250 var count = 0; | |
| 251 for (var i = 0; i < values.length; ++i) { | |
| 252 ++count; | |
| 253 this.cast(values[i]).when( | |
| 254 function(x) { if (--count === 0) deferred.resolve() }, | |
| 255 function(r) { if (count > 0) { count = 0; deferred.reject(r) } } | |
| 256 ); | |
| 257 } | |
| 258 return deferred.promise; | |
| 259 } | |
| 260 | |
| 261 function PromiseOne(values) { | |
| 262 var deferred = this.deferred(); | |
| 263 var done = false; | |
| 264 for (var i = 0; i < values.length; ++i) { | |
| 265 this.cast(values[i]).when( | |
| 266 function(x) { if (!done) { done = true; deferred.resolve(x) } }, | |
| 267 function(r) { if (!done) { done = true; deferred.reject(r) } } | |
| 268 ); | |
| 269 } | |
| 270 return deferred.promise; | |
| 271 } | |
| 272 | |
| 273 //------------------------------------------------------------------- | |
| 274 | |
| 275 function SetUpPromise() { | |
| 276 %CheckIsBootstrapping() | |
| 277 global.Promise = $Promise; | |
| 278 InstallFunctions($Promise, DONT_ENUM, [ | |
| 279 "deferred", PromiseDeferred, | |
| 280 "resolved", PromiseResolved, | |
| 281 "rejected", PromiseRejected, | |
| 282 "all", PromiseAll, | |
| 283 "one", PromiseOne, | |
|
yhirano
2013/11/18 03:59:04
Is this Promise.race?
rossberg
2013/11/18 10:56:05
Yes, added a comment.
| |
| 284 "cast", PromiseCast | |
| 285 ]); | |
| 286 InstallFunctions($Promise.prototype, DONT_ENUM, [ | |
| 287 "when", PromiseWhen, | |
|
yhirano
2013/11/18 03:59:04
There is no Promise.prototype.when in the spec dra
yhirano
2013/11/18 04:01:31
Oh, I didn't read the description. Never mind.
| |
| 288 "then", PromiseThen, | |
| 289 "catch", PromiseCatch | |
| 290 ]); | |
| 291 } | |
| 292 | |
| 293 SetUpPromise(); | |
| OLD | NEW |