| OLD | NEW |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 var $promiseCreate; |
| 6 var $promiseResolve; |
| 7 var $promiseReject; |
| 8 var $promiseChain; |
| 9 var $promiseCatch; |
| 10 var $promiseThen; |
| 11 var $promiseHasUserDefinedRejectHandler; |
| 12 var $promiseStatus; |
| 13 var $promiseValue; |
| 14 |
| 15 (function() { |
| 16 |
| 5 "use strict"; | 17 "use strict"; |
| 6 | 18 |
| 7 // This file relies on the fact that the following declaration has been made | 19 %CheckIsBootstrapping(); |
| 8 // in runtime.js: | |
| 9 // var $Object = global.Object | |
| 10 | 20 |
| 11 // For bootstrapper. | 21 // ------------------------------------------------------------------- |
| 12 | |
| 13 var IsPromise; | |
| 14 var PromiseCreate; | |
| 15 var PromiseResolve; | |
| 16 var PromiseReject; | |
| 17 var PromiseChain; | |
| 18 var PromiseCatch; | |
| 19 var PromiseThen; | |
| 20 var PromiseHasRejectHandler; | |
| 21 var PromiseHasUserDefinedRejectHandler; | |
| 22 | |
| 23 // mirror-debugger.js currently uses builtins.promiseStatus. It would be nice | |
| 24 // if we could move these property names into the closure below. | |
| 25 // TODO(jkummerow/rossberg/yangguo): Find a better solution. | |
| 26 | 22 |
| 27 // Status values: 0 = pending, +1 = resolved, -1 = rejected | 23 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
| 28 var promiseStatus = GLOBAL_PRIVATE("Promise#status"); | 24 var promiseStatus = GLOBAL_PRIVATE("Promise#status"); |
| 29 var promiseValue = GLOBAL_PRIVATE("Promise#value"); | 25 var promiseValue = GLOBAL_PRIVATE("Promise#value"); |
| 30 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); | 26 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); |
| 31 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); | 27 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); |
| 32 var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); | 28 var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); |
| 33 var promiseHasHandler = %PromiseHasHandlerSymbol(); | 29 var promiseHasHandler = %PromiseHasHandlerSymbol(); |
| 34 var lastMicrotaskId = 0; | 30 var lastMicrotaskId = 0; |
| 35 | 31 |
| 36 | 32 var GlobalPromise = function Promise(resolver) { |
| 37 (function() { | 33 if (resolver === promiseRaw) return; |
| 38 | 34 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]); |
| 39 var $Promise = function Promise(resolver) { | 35 if (!IS_SPEC_FUNCTION(resolver)) |
| 40 if (resolver === promiseRaw) return; | 36 throw MakeTypeError('resolver_not_a_function', [resolver]); |
| 41 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]); | 37 var promise = PromiseInit(this); |
| 42 if (!IS_SPEC_FUNCTION(resolver)) | 38 try { |
| 43 throw MakeTypeError('resolver_not_a_function', [resolver]); | 39 %DebugPushPromise(promise, Promise); |
| 44 var promise = PromiseInit(this); | 40 resolver(function(x) { PromiseResolve(promise, x) }, |
| 41 function(r) { PromiseReject(promise, r) }); |
| 42 } catch (e) { |
| 43 PromiseReject(promise, e); |
| 44 } finally { |
| 45 %DebugPopPromise(); |
| 46 } |
| 47 } |
| 48 |
| 49 // Core functionality. |
| 50 |
| 51 function PromiseSet(promise, status, value, onResolve, onReject) { |
| 52 SET_PRIVATE(promise, promiseStatus, status); |
| 53 SET_PRIVATE(promise, promiseValue, value); |
| 54 SET_PRIVATE(promise, promiseOnResolve, onResolve); |
| 55 SET_PRIVATE(promise, promiseOnReject, onReject); |
| 56 if (DEBUG_IS_ACTIVE) { |
| 57 %DebugPromiseEvent({ promise: promise, status: status, value: value }); |
| 58 } |
| 59 return promise; |
| 60 } |
| 61 |
| 62 function PromiseCreateAndSet(status, value) { |
| 63 var promise = new GlobalPromise(promiseRaw); |
| 64 // If debug is active, notify about the newly created promise first. |
| 65 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED); |
| 66 return PromiseSet(promise, status, value); |
| 67 } |
| 68 |
| 69 function PromiseInit(promise) { |
| 70 return PromiseSet( |
| 71 promise, 0, UNDEFINED, new InternalArray, new InternalArray) |
| 72 } |
| 73 |
| 74 function PromiseDone(promise, status, value, promiseQueue) { |
| 75 if (GET_PRIVATE(promise, promiseStatus) === 0) { |
| 76 var tasks = GET_PRIVATE(promise, promiseQueue); |
| 77 if (tasks.length) PromiseEnqueue(value, tasks, status); |
| 78 PromiseSet(promise, status, value); |
| 79 } |
| 80 } |
| 81 |
| 82 function PromiseCoerce(constructor, x) { |
| 83 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { |
| 84 var then; |
| 45 try { | 85 try { |
| 46 %DebugPushPromise(promise, Promise); | 86 then = x.then; |
| 47 resolver(function(x) { PromiseResolve(promise, x) }, | 87 } catch(r) { |
| 48 function(r) { PromiseReject(promise, r) }); | 88 return %_CallFunction(constructor, r, PromiseRejected); |
| 49 } catch (e) { | 89 } |
| 50 PromiseReject(promise, e); | 90 if (IS_SPEC_FUNCTION(then)) { |
| 51 } finally { | 91 var deferred = %_CallFunction(constructor, PromiseDeferred); |
| 52 %DebugPopPromise(); | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 // Core functionality. | |
| 57 | |
| 58 function PromiseSet(promise, status, value, onResolve, onReject) { | |
| 59 SET_PRIVATE(promise, promiseStatus, status); | |
| 60 SET_PRIVATE(promise, promiseValue, value); | |
| 61 SET_PRIVATE(promise, promiseOnResolve, onResolve); | |
| 62 SET_PRIVATE(promise, promiseOnReject, onReject); | |
| 63 if (DEBUG_IS_ACTIVE) { | |
| 64 %DebugPromiseEvent({ promise: promise, status: status, value: value }); | |
| 65 } | |
| 66 return promise; | |
| 67 } | |
| 68 | |
| 69 function PromiseCreateAndSet(status, value) { | |
| 70 var promise = new $Promise(promiseRaw); | |
| 71 // If debug is active, notify about the newly created promise first. | |
| 72 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED); | |
| 73 return PromiseSet(promise, status, value); | |
| 74 } | |
| 75 | |
| 76 function PromiseInit(promise) { | |
| 77 return PromiseSet( | |
| 78 promise, 0, UNDEFINED, new InternalArray, new InternalArray) | |
| 79 } | |
| 80 | |
| 81 function PromiseDone(promise, status, value, promiseQueue) { | |
| 82 if (GET_PRIVATE(promise, promiseStatus) === 0) { | |
| 83 var tasks = GET_PRIVATE(promise, promiseQueue); | |
| 84 if (tasks.length) PromiseEnqueue(value, tasks, status); | |
| 85 PromiseSet(promise, status, value); | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 function PromiseCoerce(constructor, x) { | |
| 90 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { | |
| 91 var then; | |
| 92 try { | 92 try { |
| 93 then = x.then; | 93 %_CallFunction(x, deferred.resolve, deferred.reject, then); |
| 94 } catch(r) { | 94 } catch(r) { |
| 95 return %_CallFunction(constructor, r, PromiseRejected); | 95 deferred.reject(r); |
| 96 } | 96 } |
| 97 if (IS_SPEC_FUNCTION(then)) { | 97 return deferred.promise; |
| 98 var deferred = %_CallFunction(constructor, PromiseDeferred); | 98 } |
| 99 try { | 99 } |
| 100 %_CallFunction(x, deferred.resolve, deferred.reject, then); | 100 return x; |
| 101 } catch(r) { | 101 } |
| 102 deferred.reject(r); | 102 |
| 103 } | 103 function PromiseHandle(value, handler, deferred) { |
| 104 return deferred.promise; | 104 try { |
| 105 %DebugPushPromise(deferred.promise, PromiseHandle); |
| 106 DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler); |
| 107 var result = handler(value); |
| 108 if (result === deferred.promise) |
| 109 throw MakeTypeError('promise_cyclic', [result]); |
| 110 else if (IsPromise(result)) |
| 111 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain); |
| 112 else |
| 113 deferred.resolve(result); |
| 114 } catch (exception) { |
| 115 try { deferred.reject(exception); } catch (e) { } |
| 116 } finally { |
| 117 %DebugPopPromise(); |
| 118 } |
| 119 } |
| 120 |
| 121 function PromiseEnqueue(value, tasks, status) { |
| 122 var id, name, instrumenting = DEBUG_IS_ACTIVE; |
| 123 %EnqueueMicrotask(function() { |
| 124 if (instrumenting) { |
| 125 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); |
| 126 } |
| 127 for (var i = 0; i < tasks.length; i += 2) { |
| 128 PromiseHandle(value, tasks[i], tasks[i + 1]) |
| 129 } |
| 130 if (instrumenting) { |
| 131 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); |
| 132 } |
| 133 }); |
| 134 if (instrumenting) { |
| 135 id = ++lastMicrotaskId; |
| 136 name = status > 0 ? "Promise.resolve" : "Promise.reject"; |
| 137 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); |
| 138 } |
| 139 } |
| 140 |
| 141 function PromiseIdResolveHandler(x) { return x } |
| 142 function PromiseIdRejectHandler(r) { throw r } |
| 143 |
| 144 function PromiseNopResolver() {} |
| 145 |
| 146 // ------------------------------------------------------------------- |
| 147 // Define exported functions. |
| 148 |
| 149 // For bootstrapper. |
| 150 |
| 151 function IsPromise(x) { |
| 152 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus); |
| 153 } |
| 154 |
| 155 function PromiseCreate() { |
| 156 return new GlobalPromise(PromiseNopResolver) |
| 157 } |
| 158 |
| 159 function PromiseResolve(promise, x) { |
| 160 PromiseDone(promise, +1, x, promiseOnResolve) |
| 161 } |
| 162 |
| 163 function PromiseReject(promise, r) { |
| 164 // Check promise status to confirm that this reject has an effect. |
| 165 // Call runtime for callbacks to the debugger or for unhandled reject. |
| 166 if (GET_PRIVATE(promise, promiseStatus) == 0) { |
| 167 var debug_is_active = DEBUG_IS_ACTIVE; |
| 168 if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) { |
| 169 %PromiseRejectEvent(promise, r, debug_is_active); |
| 170 } |
| 171 } |
| 172 PromiseDone(promise, -1, r, promiseOnReject) |
| 173 } |
| 174 |
| 175 // Convenience. |
| 176 |
| 177 function PromiseDeferred() { |
| 178 if (this === GlobalPromise) { |
| 179 // Optimized case, avoid extra closure. |
| 180 var promise = PromiseInit(new GlobalPromise(promiseRaw)); |
| 181 return { |
| 182 promise: promise, |
| 183 resolve: function(x) { PromiseResolve(promise, x) }, |
| 184 reject: function(r) { PromiseReject(promise, r) } |
| 185 }; |
| 186 } else { |
| 187 var result = {}; |
| 188 result.promise = new this(function(resolve, reject) { |
| 189 result.resolve = resolve; |
| 190 result.reject = reject; |
| 191 }) |
| 192 return result; |
| 193 } |
| 194 } |
| 195 |
| 196 function PromiseResolved(x) { |
| 197 if (this === GlobalPromise) { |
| 198 // Optimized case, avoid extra closure. |
| 199 return PromiseCreateAndSet(+1, x); |
| 200 } else { |
| 201 return new this(function(resolve, reject) { resolve(x) }); |
| 202 } |
| 203 } |
| 204 |
| 205 function PromiseRejected(r) { |
| 206 var promise; |
| 207 if (this === GlobalPromise) { |
| 208 // Optimized case, avoid extra closure. |
| 209 promise = PromiseCreateAndSet(-1, r); |
| 210 // The debug event for this would always be an uncaught promise reject, |
| 211 // which is usually simply noise. Do not trigger that debug event. |
| 212 %PromiseRejectEvent(promise, r, false); |
| 213 } else { |
| 214 promise = new this(function(resolve, reject) { reject(r) }); |
| 215 } |
| 216 return promise; |
| 217 } |
| 218 |
| 219 // Simple chaining. |
| 220 |
| 221 function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
| 222 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| 223 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
| 224 var deferred = %_CallFunction(this.constructor, PromiseDeferred); |
| 225 switch (GET_PRIVATE(this, promiseStatus)) { |
| 226 case UNDEFINED: |
| 227 throw MakeTypeError('not_a_promise', [this]); |
| 228 case 0: // Pending |
| 229 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); |
| 230 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); |
| 231 break; |
| 232 case +1: // Resolved |
| 233 PromiseEnqueue(GET_PRIVATE(this, promiseValue), |
| 234 [onResolve, deferred], |
| 235 +1); |
| 236 break; |
| 237 case -1: // Rejected |
| 238 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) { |
| 239 // Promise has already been rejected, but had no handler. |
| 240 // Revoke previously triggered reject event. |
| 241 %PromiseRevokeReject(this); |
| 105 } | 242 } |
| 106 } | 243 PromiseEnqueue(GET_PRIVATE(this, promiseValue), |
| 107 return x; | 244 [onReject, deferred], |
| 108 } | 245 -1); |
| 109 | 246 break; |
| 110 function PromiseHandle(value, handler, deferred) { | 247 } |
| 111 try { | 248 // Mark this promise as having handler. |
| 112 %DebugPushPromise(deferred.promise, PromiseHandle); | 249 SET_PRIVATE(this, promiseHasHandler, true); |
| 113 DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler); | 250 if (DEBUG_IS_ACTIVE) { |
| 114 var result = handler(value); | 251 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); |
| 115 if (result === deferred.promise) | 252 } |
| 116 throw MakeTypeError('promise_cyclic', [result]); | 253 return deferred.promise; |
| 117 else if (IsPromise(result)) | 254 } |
| 118 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain); | 255 |
| 119 else | 256 function PromiseCatch(onReject) { |
| 120 deferred.resolve(result); | 257 return this.then(UNDEFINED, onReject); |
| 121 } catch (exception) { | 258 } |
| 122 try { deferred.reject(exception); } catch (e) { } | 259 |
| 123 } finally { | 260 // Multi-unwrapped chaining with thenable coercion. |
| 124 %DebugPopPromise(); | 261 |
| 125 } | 262 function PromiseThen(onResolve, onReject) { |
| 126 } | 263 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve |
| 127 | 264 : PromiseIdResolveHandler; |
| 128 function PromiseEnqueue(value, tasks, status) { | 265 onReject = IS_SPEC_FUNCTION(onReject) ? onReject |
| 129 var id, name, instrumenting = DEBUG_IS_ACTIVE; | 266 : PromiseIdRejectHandler; |
| 130 %EnqueueMicrotask(function() { | 267 var that = this; |
| 131 if (instrumenting) { | 268 var constructor = this.constructor; |
| 132 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); | 269 return %_CallFunction( |
| 270 this, |
| 271 function(x) { |
| 272 x = PromiseCoerce(constructor, x); |
| 273 if (x === that) { |
| 274 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); |
| 275 return onReject(MakeTypeError('promise_cyclic', [x])); |
| 276 } else if (IsPromise(x)) { |
| 277 return x.then(onResolve, onReject); |
| 278 } else { |
| 279 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); |
| 280 return onResolve(x); |
| 133 } | 281 } |
| 134 for (var i = 0; i < tasks.length; i += 2) { | 282 }, |
| 135 PromiseHandle(value, tasks[i], tasks[i + 1]) | 283 onReject, |
| 136 } | 284 PromiseChain |
| 137 if (instrumenting) { | 285 ); |
| 138 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); | 286 } |
| 139 } | 287 |
| 140 }); | 288 // Combinators. |
| 141 if (instrumenting) { | 289 |
| 142 id = ++lastMicrotaskId; | 290 function PromiseCast(x) { |
| 143 name = status > 0 ? "Promise.resolve" : "Promise.reject"; | 291 // TODO(rossberg): cannot do better until we support @@create. |
| 144 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); | 292 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); |
| 145 } | 293 } |
| 146 } | 294 |
| 147 | 295 function PromiseAll(iterable) { |
| 148 function PromiseIdResolveHandler(x) { return x } | 296 var deferred = %_CallFunction(this, PromiseDeferred); |
| 149 function PromiseIdRejectHandler(r) { throw r } | 297 var resolutions = []; |
| 150 | 298 try { |
| 151 function PromiseNopResolver() {} | 299 var count = 0; |
| 152 | 300 var i = 0; |
| 153 // ------------------------------------------------------------------- | 301 for (var value of iterable) { |
| 154 // Define exported functions. | 302 this.resolve(value).then( |
| 155 | 303 // Nested scope to get closure over current i. |
| 156 // For bootstrapper. | 304 // TODO(arv): Use an inner let binding once available. |
| 157 | 305 (function(i) { |
| 158 IsPromise = function IsPromise(x) { | 306 return function(x) { |
| 159 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus); | 307 resolutions[i] = x; |
| 160 } | 308 if (--count === 0) deferred.resolve(resolutions); |
| 161 | 309 } |
| 162 PromiseCreate = function PromiseCreate() { | 310 })(i), |
| 163 return new $Promise(PromiseNopResolver) | 311 function(r) { deferred.reject(r); }); |
| 164 } | 312 ++i; |
| 165 | 313 ++count; |
| 166 PromiseResolve = function PromiseResolve(promise, x) { | 314 } |
| 167 PromiseDone(promise, +1, x, promiseOnResolve) | 315 |
| 168 } | 316 if (count === 0) { |
| 169 | 317 deferred.resolve(resolutions); |
| 170 PromiseReject = function PromiseReject(promise, r) { | 318 } |
| 171 // Check promise status to confirm that this reject has an effect. | 319 |
| 172 // Call runtime for callbacks to the debugger or for unhandled reject. | 320 } catch (e) { |
| 173 if (GET_PRIVATE(promise, promiseStatus) == 0) { | 321 deferred.reject(e) |
| 174 var debug_is_active = DEBUG_IS_ACTIVE; | 322 } |
| 175 if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) { | 323 return deferred.promise; |
| 176 %PromiseRejectEvent(promise, r, debug_is_active); | 324 } |
| 177 } | 325 |
| 178 } | 326 function PromiseRace(iterable) { |
| 179 PromiseDone(promise, -1, r, promiseOnReject) | 327 var deferred = %_CallFunction(this, PromiseDeferred); |
| 180 } | 328 try { |
| 181 | 329 for (var value of iterable) { |
| 182 // Convenience. | 330 this.resolve(value).then( |
| 183 | 331 function(x) { deferred.resolve(x) }, |
| 184 function PromiseDeferred() { | 332 function(r) { deferred.reject(r) }); |
| 185 if (this === $Promise) { | 333 } |
| 186 // Optimized case, avoid extra closure. | 334 } catch (e) { |
| 187 var promise = PromiseInit(new $Promise(promiseRaw)); | 335 deferred.reject(e) |
| 188 return { | 336 } |
| 189 promise: promise, | 337 return deferred.promise; |
| 190 resolve: function(x) { PromiseResolve(promise, x) }, | 338 } |
| 191 reject: function(r) { PromiseReject(promise, r) } | 339 |
| 192 }; | 340 |
| 193 } else { | 341 // Utility for debugger |
| 194 var result = {}; | 342 |
| 195 result.promise = new this(function(resolve, reject) { | 343 function PromiseHasUserDefinedRejectHandlerRecursive(promise) { |
| 196 result.resolve = resolve; | 344 var queue = GET_PRIVATE(promise, promiseOnReject); |
| 197 result.reject = reject; | 345 if (IS_UNDEFINED(queue)) return false; |
| 198 }) | 346 for (var i = 0; i < queue.length; i += 2) { |
| 199 return result; | 347 if (queue[i] != PromiseIdRejectHandler) return true; |
| 200 } | 348 if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) { |
| 201 } | 349 return true; |
| 202 | 350 } |
| 203 function PromiseResolved(x) { | 351 } |
| 204 if (this === $Promise) { | 352 return false; |
| 205 // Optimized case, avoid extra closure. | 353 } |
| 206 return PromiseCreateAndSet(+1, x); | 354 |
| 207 } else { | 355 // Return whether the promise will be handled by a user-defined reject |
| 208 return new this(function(resolve, reject) { resolve(x) }); | 356 // handler somewhere down the promise chain. For this, we do a depth-first |
| 209 } | 357 // search for a reject handler that's not the default PromiseIdRejectHandler. |
| 210 } | 358 function PromiseHasUserDefinedRejectHandler() { |
| 211 | 359 return PromiseHasUserDefinedRejectHandlerRecursive(this); |
| 212 function PromiseRejected(r) { | 360 }; |
| 213 var promise; | 361 |
| 214 if (this === $Promise) { | 362 // ------------------------------------------------------------------- |
| 215 // Optimized case, avoid extra closure. | 363 // Install exported functions. |
| 216 promise = PromiseCreateAndSet(-1, r); | 364 |
| 217 // The debug event for this would always be an uncaught promise reject, | 365 %AddNamedProperty(global, 'Promise', GlobalPromise, DONT_ENUM); |
| 218 // which is usually simply noise. Do not trigger that debug event. | 366 %AddNamedProperty(GlobalPromise.prototype, symbolToStringTag, "Promise", |
| 219 %PromiseRejectEvent(promise, r, false); | 367 DONT_ENUM | READ_ONLY); |
| 220 } else { | 368 |
| 221 promise = new this(function(resolve, reject) { reject(r) }); | 369 InstallFunctions(GlobalPromise, DONT_ENUM, [ |
| 222 } | 370 "defer", PromiseDeferred, |
| 223 return promise; | 371 "accept", PromiseResolved, |
| 224 } | 372 "reject", PromiseRejected, |
| 225 | 373 "all", PromiseAll, |
| 226 // Simple chaining. | 374 "race", PromiseRace, |
| 227 | 375 "resolve", PromiseCast |
| 228 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a. | 376 ]); |
| 229 // flatMap | 377 |
| 230 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; | 378 InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ |
| 231 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; | 379 "chain", PromiseChain, |
| 232 var deferred = %_CallFunction(this.constructor, PromiseDeferred); | 380 "then", PromiseThen, |
| 233 switch (GET_PRIVATE(this, promiseStatus)) { | 381 "catch", PromiseCatch |
| 234 case UNDEFINED: | 382 ]); |
| 235 throw MakeTypeError('not_a_promise', [this]); | 383 |
| 236 case 0: // Pending | 384 $promiseCreate = PromiseCreate; |
| 237 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); | 385 $promiseResolve = PromiseResolve; |
| 238 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); | 386 $promiseReject = PromiseReject; |
| 239 break; | 387 $promiseChain = PromiseChain; |
| 240 case +1: // Resolved | 388 $promiseCatch = PromiseCatch; |
| 241 PromiseEnqueue(GET_PRIVATE(this, promiseValue), | 389 $promiseThen = PromiseThen; |
| 242 [onResolve, deferred], | 390 $promiseHasUserDefinedRejectHandler = PromiseHasUserDefinedRejectHandler; |
| 243 +1); | 391 $promiseStatus = promiseStatus; |
| 244 break; | 392 $promiseValue = promiseValue; |
| 245 case -1: // Rejected | |
| 246 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) { | |
| 247 // Promise has already been rejected, but had no handler. | |
| 248 // Revoke previously triggered reject event. | |
| 249 %PromiseRevokeReject(this); | |
| 250 } | |
| 251 PromiseEnqueue(GET_PRIVATE(this, promiseValue), | |
| 252 [onReject, deferred], | |
| 253 -1); | |
| 254 break; | |
| 255 } | |
| 256 // Mark this promise as having handler. | |
| 257 SET_PRIVATE(this, promiseHasHandler, true); | |
| 258 if (DEBUG_IS_ACTIVE) { | |
| 259 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); | |
| 260 } | |
| 261 return deferred.promise; | |
| 262 } | |
| 263 | |
| 264 PromiseCatch = function PromiseCatch(onReject) { | |
| 265 return this.then(UNDEFINED, onReject); | |
| 266 } | |
| 267 | |
| 268 // Multi-unwrapped chaining with thenable coercion. | |
| 269 | |
| 270 PromiseThen = function PromiseThen(onResolve, onReject) { | |
| 271 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve | |
| 272 : PromiseIdResolveHandler; | |
| 273 onReject = IS_SPEC_FUNCTION(onReject) ? onReject | |
| 274 : PromiseIdRejectHandler; | |
| 275 var that = this; | |
| 276 var constructor = this.constructor; | |
| 277 return %_CallFunction( | |
| 278 this, | |
| 279 function(x) { | |
| 280 x = PromiseCoerce(constructor, x); | |
| 281 if (x === that) { | |
| 282 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); | |
| 283 return onReject(MakeTypeError('promise_cyclic', [x])); | |
| 284 } else if (IsPromise(x)) { | |
| 285 return x.then(onResolve, onReject); | |
| 286 } else { | |
| 287 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); | |
| 288 return onResolve(x); | |
| 289 } | |
| 290 }, | |
| 291 onReject, | |
| 292 PromiseChain | |
| 293 ); | |
| 294 } | |
| 295 | |
| 296 // Combinators. | |
| 297 | |
| 298 function PromiseCast(x) { | |
| 299 // TODO(rossberg): cannot do better until we support @@create. | |
| 300 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); | |
| 301 } | |
| 302 | |
| 303 function PromiseAll(iterable) { | |
| 304 var deferred = %_CallFunction(this, PromiseDeferred); | |
| 305 var resolutions = []; | |
| 306 try { | |
| 307 var count = 0; | |
| 308 var i = 0; | |
| 309 for (var value of iterable) { | |
| 310 this.resolve(value).then( | |
| 311 // Nested scope to get closure over current i. | |
| 312 // TODO(arv): Use an inner let binding once available. | |
| 313 (function(i) { | |
| 314 return function(x) { | |
| 315 resolutions[i] = x; | |
| 316 if (--count === 0) deferred.resolve(resolutions); | |
| 317 } | |
| 318 })(i), | |
| 319 function(r) { deferred.reject(r); }); | |
| 320 ++i; | |
| 321 ++count; | |
| 322 } | |
| 323 | |
| 324 if (count === 0) { | |
| 325 deferred.resolve(resolutions); | |
| 326 } | |
| 327 | |
| 328 } catch (e) { | |
| 329 deferred.reject(e) | |
| 330 } | |
| 331 return deferred.promise; | |
| 332 } | |
| 333 | |
| 334 function PromiseRace(iterable) { | |
| 335 var deferred = %_CallFunction(this, PromiseDeferred); | |
| 336 try { | |
| 337 for (var value of iterable) { | |
| 338 this.resolve(value).then( | |
| 339 function(x) { deferred.resolve(x) }, | |
| 340 function(r) { deferred.reject(r) }); | |
| 341 } | |
| 342 } catch (e) { | |
| 343 deferred.reject(e) | |
| 344 } | |
| 345 return deferred.promise; | |
| 346 } | |
| 347 | |
| 348 | |
| 349 // Utility for debugger | |
| 350 | |
| 351 function PromiseHasUserDefinedRejectHandlerRecursive(promise) { | |
| 352 var queue = GET_PRIVATE(promise, promiseOnReject); | |
| 353 if (IS_UNDEFINED(queue)) return false; | |
| 354 for (var i = 0; i < queue.length; i += 2) { | |
| 355 if (queue[i] != PromiseIdRejectHandler) return true; | |
| 356 if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) { | |
| 357 return true; | |
| 358 } | |
| 359 } | |
| 360 return false; | |
| 361 } | |
| 362 | |
| 363 // Return whether the promise will be handled by a user-defined reject | |
| 364 // handler somewhere down the promise chain. For this, we do a depth-first | |
| 365 // search for a reject handler that's not the default PromiseIdRejectHandler. | |
| 366 PromiseHasUserDefinedRejectHandler = | |
| 367 function PromiseHasUserDefinedRejectHandler() { | |
| 368 return PromiseHasUserDefinedRejectHandlerRecursive(this); | |
| 369 }; | |
| 370 | |
| 371 // ------------------------------------------------------------------- | |
| 372 // Install exported functions. | |
| 373 | |
| 374 %CheckIsBootstrapping(); | |
| 375 %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM); | |
| 376 %AddNamedProperty( | |
| 377 $Promise.prototype, symbolToStringTag, "Promise", DONT_ENUM | READ_ONLY); | |
| 378 InstallFunctions($Promise, DONT_ENUM, [ | |
| 379 "defer", PromiseDeferred, | |
| 380 "accept", PromiseResolved, | |
| 381 "reject", PromiseRejected, | |
| 382 "all", PromiseAll, | |
| 383 "race", PromiseRace, | |
| 384 "resolve", PromiseCast | |
| 385 ]); | |
| 386 InstallFunctions($Promise.prototype, DONT_ENUM, [ | |
| 387 "chain", PromiseChain, | |
| 388 "then", PromiseThen, | |
| 389 "catch", PromiseCatch | |
| 390 ]); | |
| 391 | 393 |
| 392 })(); | 394 })(); |
| OLD | NEW |