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 try { deferred.reject(e) } catch(e) {} |
| 192 } |
| 193 } |
| 194 } |
| 195 } |
| 196 } |
| 197 RunMicrotasks.runners.push(PromiseMicrotasksRunner); |
| 198 |
| 199 |
| 200 // Extended functionality for multi-unwrapping chaining and coercive 'then'. |
| 201 |
| 202 function PromiseThen(onResolve, onReject) { |
| 203 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; |
| 204 var that = this; |
| 205 var constructor = this.constructor; |
| 206 return this.when( |
| 207 function(x) { |
| 208 x = PromiseCoerce(constructor, x); |
| 209 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : |
| 210 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x); |
| 211 }, |
| 212 onReject |
| 213 ); |
| 214 } |
| 215 |
| 216 PromiseCoerce.table = new $WeakMap; |
| 217 |
| 218 function PromiseCoerce(constructor, x) { |
| 219 if (IsPromise(x)) { |
| 220 return x; |
| 221 } else if (!IS_NULL_OR_UNDEFINED(x) && 'then' in TO_OBJECT_INLINE(x)) { |
| 222 if (PromiseCoerce.table.has(x)) { |
| 223 return PromiseCoerce.table.get(x); |
| 224 } else { |
| 225 var deferred = constructor.deferred(); |
| 226 PromiseCoerce.table.set(x, deferred.promise); |
| 227 try { |
| 228 x.then(deferred.resolve, deferred.reject); |
| 229 } catch(e) { |
| 230 deferred.reject(e); |
| 231 } |
| 232 return deferred.promise; |
| 233 } |
| 234 } else { |
| 235 return x; |
| 236 } |
| 237 } |
| 238 |
| 239 |
| 240 // Combinators. |
| 241 |
| 242 function PromiseCast(x) { |
| 243 // TODO(rossberg): cannot do better until we support @@create. |
| 244 return IsPromise(x) ? x : this.resolved(x); |
| 245 } |
| 246 |
| 247 function PromiseAll(values) { |
| 248 var deferred = this.deferred(); |
| 249 var count = 0; |
| 250 for (var i = 0; i < values.length; ++i) { |
| 251 ++count; |
| 252 this.cast(values[i]).when( |
| 253 function(x) { if (--count === 0) deferred.resolve() }, |
| 254 function(r) { if (count > 0) { count = 0; deferred.reject(r) } } |
| 255 ); |
| 256 } |
| 257 return deferred.promise; |
| 258 } |
| 259 |
| 260 function PromiseOne(values) { |
| 261 var deferred = this.deferred(); |
| 262 var done = false; |
| 263 for (var i = 0; i < values.length; ++i) { |
| 264 this.cast(values[i]).when( |
| 265 function(x) { if (!done) { done = true; deferred.resolve(x) } }, |
| 266 function(r) { if (!done) { done = true; deferred.reject(r) } } |
| 267 ); |
| 268 } |
| 269 return deferred.promise; |
| 270 } |
| 271 |
| 272 //------------------------------------------------------------------- |
| 273 |
| 274 function SetUpPromise() { |
| 275 %CheckIsBootstrapping() |
| 276 global.Promise = $Promise; |
| 277 InstallFunctions($Promise, DONT_ENUM, [ |
| 278 "deferred", PromiseDeferred, |
| 279 "resolved", PromiseResolved, |
| 280 "rejected", PromiseRejected, |
| 281 "all", PromiseAll, |
| 282 "one", PromiseOne, |
| 283 "cast", PromiseCast |
| 284 ]); |
| 285 InstallFunctions($Promise.prototype, DONT_ENUM, [ |
| 286 "when", PromiseWhen, |
| 287 "then", PromiseThen, |
| 288 "catch", PromiseCatch |
| 289 ]); |
| 290 } |
| 291 |
| 292 SetUpPromise(); |
OLD | NEW |