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 14var InternalArray = utils.InternalArray; 15var MakeTypeError; 16var promiseCombinedDeferredSymbol = 17 utils.ImportNow("promise_combined_deferred_symbol"); 18var promiseHasHandlerSymbol = 19 utils.ImportNow("promise_has_handler_symbol"); 20var promiseRejectReactionsSymbol = 21 utils.ImportNow("promise_reject_reactions_symbol"); 22var promiseFulfillReactionsSymbol = 23 utils.ImportNow("promise_fulfill_reactions_symbol"); 24var promiseDeferredReactionsSymbol = 25 utils.ImportNow("promise_deferred_reactions_symbol"); 26var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); 27var promiseStateSymbol = utils.ImportNow("promise_state_symbol"); 28var promiseResultSymbol = utils.ImportNow("promise_result_symbol"); 29var SpeciesConstructor; 30var speciesSymbol = utils.ImportNow("species_symbol"); 31var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); 32 33utils.Import(function(from) { 34 MakeTypeError = from.MakeTypeError; 35 SpeciesConstructor = from.SpeciesConstructor; 36}); 37 38// ------------------------------------------------------------------- 39 40// [[PromiseState]] values: 41const kPending = 0; 42const kFulfilled = +1; 43const kRejected = -1; 44 45var lastMicrotaskId = 0; 46 47// ES#sec-createresolvingfunctions 48// CreateResolvingFunctions ( promise ) 49function CreateResolvingFunctions(promise) { 50 var alreadyResolved = false; 51 52 // ES#sec-promise-resolve-functions 53 // Promise Resolve Functions 54 var resolve = value => { 55 if (alreadyResolved === true) return; 56 alreadyResolved = true; 57 ResolvePromise(promise, value); 58 }; 59 60 // ES#sec-promise-reject-functions 61 // Promise Reject Functions 62 var reject = reason => { 63 if (alreadyResolved === true) return; 64 alreadyResolved = true; 65 RejectPromise(promise, reason); 66 }; 67 68 return { 69 __proto__: null, 70 resolve: resolve, 71 reject: reject 72 }; 73} 74 75 76// ES#sec-promise-executor 77// Promise ( executor ) 78var GlobalPromise = function Promise(executor) { 79 if (executor === promiseRawSymbol) { 80 return %_NewObject(GlobalPromise, new.target); 81 } 82 if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this); 83 if (!IS_CALLABLE(executor)) { 84 throw MakeTypeError(kResolverNotAFunction, executor); 85 } 86 87 var promise = PromiseInit(%_NewObject(GlobalPromise, new.target)); 88 var callbacks = CreateResolvingFunctions(promise); 89 var debug_is_active = DEBUG_IS_ACTIVE; 90 try { 91 if (debug_is_active) %DebugPushPromise(promise, Promise); 92 executor(callbacks.resolve, callbacks.reject); 93 } catch (e) { 94 %_Call(callbacks.reject, UNDEFINED, e); 95 } finally { 96 if (debug_is_active) %DebugPopPromise(); 97 } 98 99 return promise; 100} 101 102// Core functionality. 103 104function PromiseSet(promise, status, value) { 105 SET_PRIVATE(promise, promiseStateSymbol, status); 106 SET_PRIVATE(promise, promiseResultSymbol, value); 107 108 // There are 3 possible states for the resolve, reject symbols when we add 109 // a new callback -- 110 // 1) UNDEFINED -- This is the zero state where there is no callback 111 // registered. When we see this state, we directly attach the callbacks to 112 // the symbol. 113 // 2) !IS_ARRAY -- There is a single callback directly attached to the 114 // symbols. We need to create a new array to store additional callbacks. 115 // 3) IS_ARRAY -- There are multiple callbacks already registered, 116 // therefore we can just push the new callback to the existing array. 117 SET_PRIVATE(promise, promiseFulfillReactionsSymbol, UNDEFINED); 118 SET_PRIVATE(promise, promiseRejectReactionsSymbol, UNDEFINED); 119 120 // There are 2 possible states for this symbol -- 121 // 1) UNDEFINED -- This is the zero state, no deferred object is 122 // attached to this symbol. When we want to add a new deferred we 123 // directly attach it to this symbol. 124 // 2) symbol with attached deferred object -- New deferred objects 125 // are not attached to this symbol, but instead they are directly 126 // attached to the resolve, reject callback arrays. At this point, 127 // the deferred symbol's state is stale, and the deferreds should be 128 // read from the reject, resolve callbacks. 129 SET_PRIVATE(promise, promiseDeferredReactionsSymbol, UNDEFINED); 130 131 return promise; 132} 133 134function PromiseCreateAndSet(status, value) { 135 var promise = new GlobalPromise(promiseRawSymbol); 136 // If debug is active, notify about the newly created promise first. 137 if (DEBUG_IS_ACTIVE) PromiseSet(promise, kPending, UNDEFINED); 138 return PromiseSet(promise, status, value); 139} 140 141function PromiseInit(promise) { 142 return PromiseSet(promise, kPending, UNDEFINED); 143} 144 145function FulfillPromise(promise, status, value, promiseQueue) { 146 if (GET_PRIVATE(promise, promiseStateSymbol) === kPending) { 147 var tasks = GET_PRIVATE(promise, promiseQueue); 148 if (!IS_UNDEFINED(tasks)) { 149 var tasks = GET_PRIVATE(promise, promiseQueue); 150 var deferreds = GET_PRIVATE(promise, promiseDeferredReactionsSymbol); 151 PromiseEnqueue(value, tasks, deferreds, status); 152 } 153 PromiseSet(promise, status, value); 154 } 155} 156 157function PromiseHandle(value, handler, deferred) { 158 var debug_is_active = DEBUG_IS_ACTIVE; 159 try { 160 if (debug_is_active) %DebugPushPromise(deferred.promise, PromiseHandle); 161 var result = handler(value); 162 deferred.resolve(result); 163 } catch (exception) { 164 try { deferred.reject(exception); } catch (e) { } 165 } finally { 166 if (debug_is_active) %DebugPopPromise(); 167 } 168} 169 170function PromiseEnqueue(value, tasks, deferreds, status) { 171 var id, name, instrumenting = DEBUG_IS_ACTIVE; 172 %EnqueueMicrotask(function() { 173 if (instrumenting) { 174 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); 175 } 176 if (IS_ARRAY(tasks)) { 177 for (var i = 0; i < tasks.length; i += 2) { 178 PromiseHandle(value, tasks[i], tasks[i + 1]); 179 } 180 } else { 181 PromiseHandle(value, tasks, deferreds); 182 } 183 if (instrumenting) { 184 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); 185 } 186 }); 187 if (instrumenting) { 188 id = ++lastMicrotaskId; 189 name = status === kFulfilled ? "Promise.resolve" : "Promise.reject"; 190 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); 191 } 192} 193 194function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) { 195 var maybeResolveCallbacks = 196 GET_PRIVATE(promise, promiseFulfillReactionsSymbol); 197 if (IS_UNDEFINED(maybeResolveCallbacks)) { 198 SET_PRIVATE(promise, promiseFulfillReactionsSymbol, onResolve); 199 SET_PRIVATE(promise, promiseRejectReactionsSymbol, onReject); 200 SET_PRIVATE(promise, promiseDeferredReactionsSymbol, deferred); 201 } else if (!IS_ARRAY(maybeResolveCallbacks)) { 202 var resolveCallbacks = new InternalArray(); 203 var rejectCallbacks = new InternalArray(); 204 var existingDeferred = GET_PRIVATE(promise, promiseDeferredReactionsSymbol); 205 206 resolveCallbacks.push( 207 maybeResolveCallbacks, existingDeferred, onResolve, deferred); 208 rejectCallbacks.push(GET_PRIVATE(promise, promiseRejectReactionsSymbol), 209 existingDeferred, 210 onReject, 211 deferred); 212 213 SET_PRIVATE(promise, promiseFulfillReactionsSymbol, resolveCallbacks); 214 SET_PRIVATE(promise, promiseRejectReactionsSymbol, rejectCallbacks); 215 } else { 216 maybeResolveCallbacks.push(onResolve, deferred); 217 GET_PRIVATE(promise, promiseRejectReactionsSymbol).push(onReject, deferred); 218 } 219} 220 221function PromiseIdResolveHandler(x) { return x } 222function PromiseIdRejectHandler(r) { throw r } 223 224function PromiseNopResolver() {} 225 226// ------------------------------------------------------------------- 227// Define exported functions. 228 229// For bootstrapper. 230 231// ES#sec-ispromise IsPromise ( x ) 232function IsPromise(x) { 233 return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStateSymbol); 234} 235 236function PromiseCreate() { 237 return new GlobalPromise(PromiseNopResolver) 238} 239 240// ES#sec-promise-resolve-functions 241// Promise Resolve Functions, steps 6-13 242function ResolvePromise(promise, resolution) { 243 if (resolution === promise) { 244 return RejectPromise(promise, MakeTypeError(kPromiseCyclic, resolution)); 245 } 246 if (IS_RECEIVER(resolution)) { 247 // 25.4.1.3.2 steps 8-12 248 try { 249 var then = resolution.then; 250 } catch (e) { 251 return RejectPromise(promise, e); 252 } 253 254 // Resolution is a native promise and if it's already resolved or 255 // rejected, shortcircuit the resolution procedure by directly 256 // reusing the value from the promise. 257 if (IsPromise(resolution) && then === PromiseThen) { 258 var thenableState = GET_PRIVATE(resolution, promiseStateSymbol); 259 if (thenableState === kFulfilled) { 260 // This goes inside the if-else to save one symbol lookup in 261 // the slow path. 262 var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); 263 FulfillPromise(promise, kFulfilled, thenableValue, 264 promiseFulfillReactionsSymbol); 265 SET_PRIVATE(promise, promiseHasHandlerSymbol, true); 266 return; 267 } else if (thenableState === kRejected) { 268 var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); 269 if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) { 270 // Promise has already been rejected, but had no handler. 271 // Revoke previously triggered reject event. 272 %PromiseRevokeReject(resolution); 273 } 274 RejectPromise(promise, thenableValue); 275 SET_PRIVATE(resolution, promiseHasHandlerSymbol, true); 276 return; 277 } 278 } 279 280 if (IS_CALLABLE(then)) { 281 // PromiseResolveThenableJob 282 var id; 283 var name = "PromiseResolveThenableJob"; 284 var instrumenting = DEBUG_IS_ACTIVE; 285 %EnqueueMicrotask(function() { 286 if (instrumenting) { 287 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); 288 } 289 var callbacks = CreateResolvingFunctions(promise); 290 try { 291 %_Call(then, resolution, callbacks.resolve, callbacks.reject); 292 } catch (e) { 293 %_Call(callbacks.reject, UNDEFINED, e); 294 } 295 if (instrumenting) { 296 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); 297 } 298 }); 299 if (instrumenting) { 300 id = ++lastMicrotaskId; 301 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); 302 } 303 return; 304 } 305 } 306 FulfillPromise(promise, kFulfilled, resolution, promiseFulfillReactionsSymbol); 307} 308 309// ES#sec-rejectpromise 310// RejectPromise ( promise, reason ) 311function RejectPromise(promise, reason) { 312 // Check promise status to confirm that this reject has an effect. 313 // Call runtime for callbacks to the debugger or for unhandled reject. 314 if (GET_PRIVATE(promise, promiseStateSymbol) === kPending) { 315 var debug_is_active = DEBUG_IS_ACTIVE; 316 if (debug_is_active || 317 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { 318 %PromiseRejectEvent(promise, reason, debug_is_active); 319 } 320 } 321 FulfillPromise(promise, kRejected, reason, promiseRejectReactionsSymbol) 322} 323 324// ES#sec-newpromisecapability 325// NewPromiseCapability ( C ) 326function NewPromiseCapability(C) { 327 if (C === GlobalPromise) { 328 // Optimized case, avoid extra closure. 329 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); 330 var callbacks = CreateResolvingFunctions(promise); 331 return { 332 promise: promise, 333 resolve: callbacks.resolve, 334 reject: callbacks.reject 335 }; 336 } 337 338 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; 339 result.promise = new C((resolve, reject) => { 340 if (!IS_UNDEFINED(result.resolve) || !IS_UNDEFINED(result.reject)) 341 throw MakeTypeError(kPromiseExecutorAlreadyInvoked); 342 result.resolve = resolve; 343 result.reject = reject; 344 }); 345 346 if (!IS_CALLABLE(result.resolve) || !IS_CALLABLE(result.reject)) 347 throw MakeTypeError(kPromiseNonCallable); 348 349 return result; 350} 351 352// Unspecified V8-specific legacy function 353function PromiseDefer() { 354 %IncrementUseCounter(kPromiseDefer); 355 return NewPromiseCapability(this); 356} 357 358// Unspecified V8-specific legacy function 359function PromiseAccept(x) { 360 %IncrementUseCounter(kPromiseAccept); 361 return %_Call(PromiseResolve, this, x); 362} 363 364// ES#sec-promise.reject 365// Promise.reject ( x ) 366function PromiseReject(r) { 367 if (!IS_RECEIVER(this)) { 368 throw MakeTypeError(kCalledOnNonObject, PromiseResolve); 369 } 370 if (this === GlobalPromise) { 371 // Optimized case, avoid extra closure. 372 var promise = PromiseCreateAndSet(kRejected, r); 373 // The debug event for this would always be an uncaught promise reject, 374 // which is usually simply noise. Do not trigger that debug event. 375 %PromiseRejectEvent(promise, r, false); 376 return promise; 377 } else { 378 var promiseCapability = NewPromiseCapability(this); 379 %_Call(promiseCapability.reject, UNDEFINED, r); 380 return promiseCapability.promise; 381 } 382} 383 384// Shortcut Promise.reject and Promise.resolve() implementations, used by 385// Async Functions implementation. 386function PromiseCreateRejected(r) { 387 return %_Call(PromiseReject, GlobalPromise, r); 388} 389 390function PromiseCreateResolved(x) { 391 return %_Call(PromiseResolve, GlobalPromise, x); 392} 393 394// ES#sec-promise.prototype.then 395// Promise.prototype.then ( onFulfilled, onRejected ) 396// Multi-unwrapped chaining with thenable coercion. 397function PromiseThen(onResolve, onReject) { 398 var status = GET_PRIVATE(this, promiseStateSymbol); 399 if (IS_UNDEFINED(status)) { 400 throw MakeTypeError(kNotAPromise, this); 401 } 402 403 var constructor = SpeciesConstructor(this, GlobalPromise); 404 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; 405 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; 406 var deferred = NewPromiseCapability(constructor); 407 switch (status) { 408 case kPending: 409 PromiseAttachCallbacks(this, deferred, onResolve, onReject); 410 break; 411 case kFulfilled: 412 PromiseEnqueue(GET_PRIVATE(this, promiseResultSymbol), 413 onResolve, deferred, kFulfilled); 414 break; 415 case kRejected: 416 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { 417 // Promise has already been rejected, but had no handler. 418 // Revoke previously triggered reject event. 419 %PromiseRevokeReject(this); 420 } 421 PromiseEnqueue(GET_PRIVATE(this, promiseResultSymbol), 422 onReject, deferred, kRejected); 423 break; 424 } 425 // Mark this promise as having handler. 426 SET_PRIVATE(this, promiseHasHandlerSymbol, true); 427 return deferred.promise; 428} 429 430// Unspecified V8-specific legacy function 431// Chain is left around for now as an alias for then 432function PromiseChain(onResolve, onReject) { 433 %IncrementUseCounter(kPromiseChain); 434 return %_Call(PromiseThen, this, onResolve, onReject); 435} 436 437// ES#sec-promise.prototype.catch 438// Promise.prototype.catch ( onRejected ) 439function PromiseCatch(onReject) { 440 return this.then(UNDEFINED, onReject); 441} 442 443// Combinators. 444 445// ES#sec-promise.resolve 446// Promise.resolve ( x ) 447function PromiseResolve(x) { 448 if (!IS_RECEIVER(this)) { 449 throw MakeTypeError(kCalledOnNonObject, PromiseResolve); 450 } 451 if (IsPromise(x) && x.constructor === this) return x; 452 453 var promiseCapability = NewPromiseCapability(this); 454 var resolveResult = %_Call(promiseCapability.resolve, UNDEFINED, x); 455 return promiseCapability.promise; 456} 457 458// ES#sec-promise.all 459// Promise.all ( iterable ) 460function PromiseAll(iterable) { 461 if (!IS_RECEIVER(this)) { 462 throw MakeTypeError(kCalledOnNonObject, "Promise.all"); 463 } 464 465 var deferred = NewPromiseCapability(this); 466 var resolutions = new InternalArray(); 467 var count; 468 469 function CreateResolveElementFunction(index, values, promiseCapability) { 470 var alreadyCalled = false; 471 return (x) => { 472 if (alreadyCalled === true) return; 473 alreadyCalled = true; 474 values[index] = x; 475 if (--count === 0) { 476 var valuesArray = []; 477 %MoveArrayContents(values, valuesArray); 478 %_Call(promiseCapability.resolve, UNDEFINED, valuesArray); 479 } 480 }; 481 } 482 483 try { 484 var i = 0; 485 count = 1; 486 for (var value of iterable) { 487 var nextPromise = this.resolve(value); 488 ++count; 489 nextPromise.then( 490 CreateResolveElementFunction(i, resolutions, deferred), 491 deferred.reject); 492 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); 493 ++i; 494 } 495 496 // 6.d 497 if (--count === 0) { 498 var valuesArray = []; 499 %MoveArrayContents(resolutions, valuesArray); 500 %_Call(deferred.resolve, UNDEFINED, valuesArray); 501 } 502 503 } catch (e) { 504 %_Call(deferred.reject, UNDEFINED, e); 505 } 506 return deferred.promise; 507} 508 509// ES#sec-promise.race 510// Promise.race ( iterable ) 511function PromiseRace(iterable) { 512 if (!IS_RECEIVER(this)) { 513 throw MakeTypeError(kCalledOnNonObject, PromiseRace); 514 } 515 516 var deferred = NewPromiseCapability(this); 517 try { 518 for (var value of iterable) { 519 this.resolve(value).then(deferred.resolve, deferred.reject); 520 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); 521 } 522 } catch (e) { 523 deferred.reject(e) 524 } 525 return deferred.promise; 526} 527 528 529// Utility for debugger 530 531function PromiseHasUserDefinedRejectHandlerCheck(handler, deferred) { 532 if (handler !== PromiseIdRejectHandler) { 533 var combinedDeferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol); 534 if (IS_UNDEFINED(combinedDeferred)) return true; 535 if (PromiseHasUserDefinedRejectHandlerRecursive(combinedDeferred.promise)) { 536 return true; 537 } 538 } else if (PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise)) { 539 return true; 540 } 541 return false; 542} 543 544function PromiseHasUserDefinedRejectHandlerRecursive(promise) { 545 var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol); 546 var deferreds = GET_PRIVATE(promise, promiseDeferredReactionsSymbol); 547 if (IS_UNDEFINED(queue)) return false; 548 if (!IS_ARRAY(queue)) { 549 return PromiseHasUserDefinedRejectHandlerCheck(queue, deferreds); 550 } else { 551 for (var i = 0; i < queue.length; i += 2) { 552 if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) { 553 return true; 554 } 555 } 556 } 557 return false; 558} 559 560// Return whether the promise will be handled by a user-defined reject 561// handler somewhere down the promise chain. For this, we do a depth-first 562// search for a reject handler that's not the default PromiseIdRejectHandler. 563function PromiseHasUserDefinedRejectHandler() { 564 return PromiseHasUserDefinedRejectHandlerRecursive(this); 565}; 566 567 568function PromiseSpecies() { 569 return this; 570} 571 572// ------------------------------------------------------------------- 573// Install exported functions. 574 575%AddNamedProperty(global, 'Promise', GlobalPromise, DONT_ENUM); 576%AddNamedProperty(GlobalPromise.prototype, toStringTagSymbol, "Promise", 577 DONT_ENUM | READ_ONLY); 578 579utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ 580 "reject", PromiseReject, 581 "all", PromiseAll, 582 "race", PromiseRace, 583 "resolve", PromiseResolve 584]); 585 586utils.InstallGetter(GlobalPromise, speciesSymbol, PromiseSpecies); 587 588utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ 589 "then", PromiseThen, 590 "catch", PromiseCatch 591]); 592 593%InstallToContext([ 594 "promise_catch", PromiseCatch, 595 "promise_chain", PromiseChain, 596 "promise_create", PromiseCreate, 597 "promise_has_user_defined_reject_handler", PromiseHasUserDefinedRejectHandler, 598 "promise_reject", RejectPromise, 599 "promise_resolve", ResolvePromise, 600 "promise_then", PromiseThen, 601 "promise_create_rejected", PromiseCreateRejected, 602 "promise_create_resolved", PromiseCreateResolved 603]); 604 605// This allows extras to create promises quickly without building extra 606// resolve/reject closures, and allows them to later resolve and reject any 607// promise without having to hold on to those closures forever. 608utils.InstallFunctions(extrasUtils, 0, [ 609 "createPromise", PromiseCreate, 610 "resolvePromise", ResolvePromise, 611 "rejectPromise", RejectPromise 612]); 613 614// TODO(v8:4567): Allow experimental natives to remove function prototype 615[PromiseChain, PromiseDefer, PromiseAccept].forEach( 616 fn => %FunctionRemovePrototype(fn)); 617 618utils.Export(function(to) { 619 to.PromiseChain = PromiseChain; 620 to.PromiseDefer = PromiseDefer; 621 to.PromiseAccept = PromiseAccept; 622 623 to.PromiseCreateRejected = PromiseCreateRejected; 624 to.PromiseCreateResolved = PromiseCreateResolved; 625 to.PromiseThen = PromiseThen; 626}); 627 628}) 629