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