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 'chain' 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 // TODO(rossberg): current draft makes exception from this call asynchronous, |
| 69 // but that's probably a mistake. |
| 70 } |
| 71 |
| 72 function PromiseSet(promise, status, value, onResolve, onReject) { |
| 73 SET_PRIVATE(promise, promiseStatus, status); |
| 74 SET_PRIVATE(promise, promiseValue, value); |
| 75 SET_PRIVATE(promise, promiseOnResolve, onResolve); |
| 76 SET_PRIVATE(promise, promiseOnReject, onReject); |
| 77 return promise; |
| 78 } |
| 79 |
| 80 function PromiseInit(promise) { |
| 81 return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray) |
| 82 } |
| 83 |
| 84 function PromiseDone(promise, status, value, promiseQueue) { |
| 85 if (GET_PRIVATE(promise, promiseStatus) !== 0) return; |
| 86 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); |
| 87 PromiseSet(promise, status, value); |
| 88 } |
| 89 |
| 90 function PromiseResolve(promise, x) { |
| 91 PromiseDone(promise, +1, x, promiseOnResolve) |
| 92 } |
| 93 |
| 94 function PromiseReject(promise, r) { |
| 95 PromiseDone(promise, -1, r, promiseOnReject) |
| 96 } |
| 97 |
| 98 |
| 99 // Convenience. |
| 100 |
| 101 function PromiseDeferred() { |
| 102 if (this === $Promise) { |
| 103 // Optimized case, avoid extra closure. |
| 104 var promise = PromiseInit(new Promise(promiseRaw)); |
| 105 return { |
| 106 promise: promise, |
| 107 resolve: function(x) { PromiseResolve(promise, x) }, |
| 108 reject: function(r) { PromiseReject(promise, r) } |
| 109 }; |
| 110 } else { |
| 111 var result = {}; |
| 112 result.promise = new this(function(resolve, reject) { |
| 113 result.resolve = resolve; |
| 114 result.reject = reject; |
| 115 }) |
| 116 return result; |
| 117 } |
| 118 } |
| 119 |
| 120 function PromiseResolved(x) { |
| 121 if (this === $Promise) { |
| 122 // Optimized case, avoid extra closure. |
| 123 return PromiseSet(new Promise(promiseRaw), +1, x); |
| 124 } else { |
| 125 return new this(function(resolve, reject) { resolve(x) }); |
| 126 } |
| 127 } |
| 128 |
| 129 function PromiseRejected(r) { |
| 130 if (this === $Promise) { |
| 131 // Optimized case, avoid extra closure. |
| 132 return PromiseSet(new Promise(promiseRaw), -1, r); |
| 133 } else { |
| 134 return new this(function(resolve, reject) { reject(r) }); |
| 135 } |
| 136 } |
| 137 |
| 138 |
| 139 // Simple chaining. |
| 140 |
| 141 function PromiseIdResolveHandler(x) { return x } |
| 142 function PromiseIdRejectHandler(r) { throw r } |
| 143 |
| 144 function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
| 145 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| 146 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
| 147 var deferred = %_CallFunction(this.constructor, PromiseDeferred); |
| 148 switch (GET_PRIVATE(this, promiseStatus)) { |
| 149 case UNDEFINED: |
| 150 throw MakeTypeError('not_a_promise', [this]); |
| 151 case 0: // Pending |
| 152 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); |
| 153 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); |
| 154 break; |
| 155 case +1: // Resolved |
| 156 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]); |
| 157 break; |
| 158 case -1: // Rejected |
| 159 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]); |
| 160 break; |
| 161 } |
| 162 return deferred.promise; |
| 163 } |
| 164 |
| 165 function PromiseCatch(onReject) { |
| 166 return this.chain(UNDEFINED, onReject); |
| 167 } |
| 168 |
| 169 function PromiseEnqueue(value, tasks) { |
| 170 promiseEvents.push(value, tasks); |
| 171 %SetMicrotaskPending(true); |
| 172 } |
| 173 |
| 174 function PromiseMicrotaskRunner() { |
| 175 var events = promiseEvents; |
| 176 if (events.length > 0) { |
| 177 promiseEvents = new InternalArray; |
| 178 for (var i = 0; i < events.length; i += 2) { |
| 179 var value = events[i]; |
| 180 var tasks = events[i + 1]; |
| 181 for (var j = 0; j < tasks.length; j += 2) { |
| 182 var handler = tasks[j]; |
| 183 var deferred = tasks[j + 1]; |
| 184 try { |
| 185 var result = handler(value); |
| 186 if (result === deferred.promise) |
| 187 throw MakeTypeError('promise_cyclic', [result]); |
| 188 else if (IsPromise(result)) |
| 189 result.chain(deferred.resolve, deferred.reject); |
| 190 else |
| 191 deferred.resolve(result); |
| 192 } catch(e) { |
| 193 // TODO(rossberg): perhaps log uncaught exceptions below. |
| 194 try { deferred.reject(e) } catch(e) {} |
| 195 } |
| 196 } |
| 197 } |
| 198 } |
| 199 } |
| 200 RunMicrotasks.runners.push(PromiseMicrotaskRunner); |
| 201 |
| 202 |
| 203 // Multi-unwrapped chaining with thenable coercion. |
| 204 |
| 205 function PromiseThen(onResolve, onReject) { |
| 206 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| 207 var that = this; |
| 208 var constructor = this.constructor; |
| 209 return this.chain( |
| 210 function(x) { |
| 211 x = PromiseCoerce(constructor, x); |
| 212 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : |
| 213 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x); |
| 214 }, |
| 215 onReject |
| 216 ); |
| 217 } |
| 218 |
| 219 PromiseCoerce.table = new $WeakMap; |
| 220 |
| 221 function PromiseCoerce(constructor, x) { |
| 222 var then; |
| 223 if (IsPromise(x)) { |
| 224 return x; |
| 225 } else if (!IS_NULL_OR_UNDEFINED(x) && %IsCallable(then = x.then)) { |
| 226 if (PromiseCoerce.table.has(x)) { |
| 227 return PromiseCoerce.table.get(x); |
| 228 } else { |
| 229 var deferred = constructor.deferred(); |
| 230 PromiseCoerce.table.set(x, deferred.promise); |
| 231 try { |
| 232 %_CallFunction(x, deferred.resolve, deferred.reject, then); |
| 233 } catch(e) { |
| 234 deferred.reject(e); |
| 235 } |
| 236 return deferred.promise; |
| 237 } |
| 238 } else { |
| 239 return x; |
| 240 } |
| 241 } |
| 242 |
| 243 |
| 244 // Combinators. |
| 245 |
| 246 function PromiseCast(x) { |
| 247 // TODO(rossberg): cannot do better until we support @@create. |
| 248 return IsPromise(x) ? x : this.resolved(x); |
| 249 } |
| 250 |
| 251 function PromiseAll(values) { |
| 252 var deferred = this.deferred(); |
| 253 var resolutions = []; |
| 254 var count = values.length; |
| 255 if (count === 0) { |
| 256 deferred.resolve(resolutions); |
| 257 } else { |
| 258 for (var i = 0; i < values.length; ++i) { |
| 259 this.cast(values[i]).chain( |
| 260 function(i, x) { |
| 261 resolutions[i] = x; |
| 262 if (--count === 0) deferred.resolve(resolutions); |
| 263 }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once available |
| 264 function(r) { |
| 265 if (count > 0) { count = 0; deferred.reject(r) } |
| 266 } |
| 267 ); |
| 268 } |
| 269 } |
| 270 return deferred.promise; |
| 271 } |
| 272 |
| 273 function PromiseOne(values) { // a.k.a. race |
| 274 var deferred = this.deferred(); |
| 275 var done = false; |
| 276 for (var i = 0; i < values.length; ++i) { |
| 277 this.cast(values[i]).chain( |
| 278 function(x) { if (!done) { done = true; deferred.resolve(x) } }, |
| 279 function(r) { if (!done) { done = true; deferred.reject(r) } } |
| 280 ); |
| 281 } |
| 282 return deferred.promise; |
| 283 } |
| 284 |
| 285 //------------------------------------------------------------------- |
| 286 |
| 287 function SetUpPromise() { |
| 288 %CheckIsBootstrapping() |
| 289 global.Promise = $Promise; |
| 290 InstallFunctions($Promise, DONT_ENUM, [ |
| 291 "deferred", PromiseDeferred, |
| 292 "resolved", PromiseResolved, |
| 293 "rejected", PromiseRejected, |
| 294 "all", PromiseAll, |
| 295 "one", PromiseOne, |
| 296 "cast", PromiseCast |
| 297 ]); |
| 298 InstallFunctions($Promise.prototype, DONT_ENUM, [ |
| 299 "chain", PromiseChain, |
| 300 "then", PromiseThen, |
| 301 "catch", PromiseCatch |
| 302 ]); |
| 303 } |
| 304 |
| 305 SetUpPromise(); |
OLD | NEW |