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 promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol"); 21var promiseOnResolveSymbol = 22 utils.ImportNow("promise_on_resolve_symbol"); 23var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); 24var promiseStatusSymbol = utils.ImportNow("promise_status_symbol"); 25var promiseValueSymbol = utils.ImportNow("promise_value_symbol"); 26var SpeciesConstructor; 27var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); 28 29utils.Import(function(from) { 30 MakeTypeError = from.MakeTypeError; 31 SpeciesConstructor = from.SpeciesConstructor; 32}); 33 34// ------------------------------------------------------------------- 35 36// Status values: 0 = pending, +1 = resolved, -1 = rejected 37var lastMicrotaskId = 0; 38 39function CreateResolvingFunctions(promise) { 40 var alreadyResolved = false; 41 42 var resolve = value => { 43 if (alreadyResolved === true) return; 44 alreadyResolved = true; 45 PromiseResolve(promise, value); 46 }; 47 48 var reject = reason => { 49 if (alreadyResolved === true) return; 50 alreadyResolved = true; 51 PromiseReject(promise, reason); 52 }; 53 54 return { 55 __proto__: null, 56 resolve: resolve, 57 reject: reject 58 }; 59} 60 61 62var GlobalPromise = function Promise(resolver) { 63 if (resolver === promiseRawSymbol) { 64 return %NewObject(GlobalPromise, new.target); 65 } 66 if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this); 67 if (!IS_CALLABLE(resolver)) 68 throw MakeTypeError(kResolverNotAFunction, resolver); 69 70 var promise = PromiseInit(%NewObject(GlobalPromise, new.target)); 71 var callbacks = CreateResolvingFunctions(promise); 72 73 try { 74 %DebugPushPromise(promise, Promise); 75 resolver(callbacks.resolve, callbacks.reject); 76 } catch (e) { 77 %_Call(callbacks.reject, UNDEFINED, e); 78 } finally { 79 %DebugPopPromise(); 80 } 81 82 return promise; 83} 84 85// Core functionality. 86 87function PromiseSet(promise, status, value, onResolve, onReject) { 88 SET_PRIVATE(promise, promiseStatusSymbol, status); 89 SET_PRIVATE(promise, promiseValueSymbol, value); 90 SET_PRIVATE(promise, promiseOnResolveSymbol, onResolve); 91 SET_PRIVATE(promise, promiseOnRejectSymbol, onReject); 92 if (DEBUG_IS_ACTIVE) { 93 %DebugPromiseEvent({ promise: promise, status: status, value: value }); 94 } 95 return promise; 96} 97 98function PromiseCreateAndSet(status, value) { 99 var promise = new GlobalPromise(promiseRawSymbol); 100 // If debug is active, notify about the newly created promise first. 101 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED); 102 return PromiseSet(promise, status, value); 103} 104 105function PromiseInit(promise) { 106 return PromiseSet( 107 promise, 0, UNDEFINED, new InternalArray, new InternalArray) 108} 109 110function PromiseDone(promise, status, value, promiseQueue) { 111 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { 112 var tasks = GET_PRIVATE(promise, promiseQueue); 113 if (tasks.length) PromiseEnqueue(value, tasks, status); 114 PromiseSet(promise, status, value); 115 } 116} 117 118function PromiseHandle(value, handler, deferred) { 119 try { 120 %DebugPushPromise(deferred.promise, PromiseHandle); 121 var result = handler(value); 122 deferred.resolve(result); 123 } catch (exception) { 124 try { deferred.reject(exception); } catch (e) { } 125 } finally { 126 %DebugPopPromise(); 127 } 128} 129 130function PromiseEnqueue(value, tasks, status) { 131 var id, name, instrumenting = DEBUG_IS_ACTIVE; 132 %EnqueueMicrotask(function() { 133 if (instrumenting) { 134 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); 135 } 136 for (var i = 0; i < tasks.length; i += 2) { 137 PromiseHandle(value, tasks[i], tasks[i + 1]) 138 } 139 if (instrumenting) { 140 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); 141 } 142 }); 143 if (instrumenting) { 144 id = ++lastMicrotaskId; 145 name = status > 0 ? "Promise.resolve" : "Promise.reject"; 146 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); 147 } 148} 149 150function PromiseIdResolveHandler(x) { return x } 151function PromiseIdRejectHandler(r) { throw r } 152 153function PromiseNopResolver() {} 154 155// ------------------------------------------------------------------- 156// Define exported functions. 157 158// For bootstrapper. 159 160function IsPromise(x) { 161 return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); 162} 163 164function PromiseCreate() { 165 return new GlobalPromise(PromiseNopResolver) 166} 167 168function PromiseResolve(promise, x) { 169 if (x === promise) { 170 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); 171 } 172 if (IS_RECEIVER(x)) { 173 // 25.4.1.3.2 steps 8-12 174 try { 175 var then = x.then; 176 } catch (e) { 177 return PromiseReject(promise, e); 178 } 179 if (IS_CALLABLE(then)) { 180 // PromiseResolveThenableJob 181 var id, name, instrumenting = DEBUG_IS_ACTIVE; 182 %EnqueueMicrotask(function() { 183 if (instrumenting) { 184 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); 185 } 186 var callbacks = CreateResolvingFunctions(promise); 187 try { 188 %_Call(then, x, callbacks.resolve, callbacks.reject); 189 } catch (e) { 190 %_Call(callbacks.reject, UNDEFINED, e); 191 } 192 if (instrumenting) { 193 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); 194 } 195 }); 196 if (instrumenting) { 197 id = ++lastMicrotaskId; 198 name = "PromseResolveThenableJob"; 199 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); 200 } 201 return; 202 } 203 } 204 PromiseDone(promise, +1, x, promiseOnResolveSymbol); 205} 206 207function PromiseReject(promise, r) { 208 // Check promise status to confirm that this reject has an effect. 209 // Call runtime for callbacks to the debugger or for unhandled reject. 210 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { 211 var debug_is_active = DEBUG_IS_ACTIVE; 212 if (debug_is_active || 213 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { 214 %PromiseRejectEvent(promise, r, debug_is_active); 215 } 216 } 217 PromiseDone(promise, -1, r, promiseOnRejectSymbol) 218} 219 220// Convenience. 221 222function NewPromiseCapability(C) { 223 if (C === GlobalPromise) { 224 // Optimized case, avoid extra closure. 225 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); 226 var callbacks = CreateResolvingFunctions(promise); 227 return { 228 promise: promise, 229 resolve: callbacks.resolve, 230 reject: callbacks.reject 231 }; 232 } 233 234 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; 235 result.promise = new C((resolve, reject) => { 236 if (!IS_UNDEFINED(result.resolve) || !IS_UNDEFINED(result.reject)) 237 throw MakeTypeError(kPromiseExecutorAlreadyInvoked); 238 result.resolve = resolve; 239 result.reject = reject; 240 }); 241 242 return result; 243} 244 245function PromiseDeferred() { 246 %IncrementUseCounter(kPromiseDefer); 247 return NewPromiseCapability(this); 248} 249 250function PromiseResolved(x) { 251 %IncrementUseCounter(kPromiseAccept); 252 return %_Call(PromiseCast, this, x); 253} 254 255function PromiseRejected(r) { 256 if (!IS_RECEIVER(this)) { 257 throw MakeTypeError(kCalledOnNonObject, PromiseRejected); 258 } 259 if (this === GlobalPromise) { 260 // Optimized case, avoid extra closure. 261 var promise = PromiseCreateAndSet(-1, r); 262 // The debug event for this would always be an uncaught promise reject, 263 // which is usually simply noise. Do not trigger that debug event. 264 %PromiseRejectEvent(promise, r, false); 265 return promise; 266 } else { 267 var promiseCapability = NewPromiseCapability(this); 268 %_Call(promiseCapability.reject, UNDEFINED, r); 269 return promiseCapability.promise; 270 } 271} 272 273// Multi-unwrapped chaining with thenable coercion. 274 275function PromiseThen(onResolve, onReject) { 276 var status = GET_PRIVATE(this, promiseStatusSymbol); 277 if (IS_UNDEFINED(status)) { 278 throw MakeTypeError(kNotAPromise, this); 279 } 280 281 var constructor = SpeciesConstructor(this, GlobalPromise); 282 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; 283 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; 284 var deferred = NewPromiseCapability(constructor); 285 switch (status) { 286 case 0: // Pending 287 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); 288 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); 289 break; 290 case +1: // Resolved 291 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 292 [onResolve, deferred], 293 +1); 294 break; 295 case -1: // Rejected 296 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { 297 // Promise has already been rejected, but had no handler. 298 // Revoke previously triggered reject event. 299 %PromiseRevokeReject(this); 300 } 301 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 302 [onReject, deferred], 303 -1); 304 break; 305 } 306 // Mark this promise as having handler. 307 SET_PRIVATE(this, promiseHasHandlerSymbol, true); 308 if (DEBUG_IS_ACTIVE) { 309 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); 310 } 311 return deferred.promise; 312} 313 314// Chain is left around for now as an alias for then 315function PromiseChain(onResolve, onReject) { 316 %IncrementUseCounter(kPromiseChain); 317 return %_Call(PromiseThen, this, onResolve, onReject); 318} 319 320function PromiseCatch(onReject) { 321 return this.then(UNDEFINED, onReject); 322} 323 324// Combinators. 325 326function PromiseCast(x) { 327 if (!IS_RECEIVER(this)) { 328 throw MakeTypeError(kCalledOnNonObject, PromiseCast); 329 } 330 if (IsPromise(x) && x.constructor === this) return x; 331 332 var promiseCapability = NewPromiseCapability(this); 333 var resolveResult = %_Call(promiseCapability.resolve, UNDEFINED, x); 334 return promiseCapability.promise; 335} 336 337function PromiseAll(iterable) { 338 if (!IS_RECEIVER(this)) { 339 throw MakeTypeError(kCalledOnNonObject, "Promise.all"); 340 } 341 342 var deferred = NewPromiseCapability(this); 343 var resolutions = new InternalArray(); 344 var count; 345 346 function CreateResolveElementFunction(index, values, promiseCapability) { 347 var alreadyCalled = false; 348 return (x) => { 349 if (alreadyCalled === true) return; 350 alreadyCalled = true; 351 values[index] = x; 352 if (--count === 0) { 353 var valuesArray = []; 354 %MoveArrayContents(values, valuesArray); 355 %_Call(promiseCapability.resolve, UNDEFINED, valuesArray); 356 } 357 }; 358 } 359 360 try { 361 var i = 0; 362 count = 1; 363 for (var value of iterable) { 364 var nextPromise = this.resolve(value); 365 ++count; 366 nextPromise.then( 367 CreateResolveElementFunction(i, resolutions, deferred), 368 deferred.reject); 369 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); 370 ++i; 371 } 372 373 // 6.d 374 if (--count === 0) { 375 var valuesArray = []; 376 %MoveArrayContents(resolutions, valuesArray); 377 %_Call(deferred.resolve, UNDEFINED, valuesArray); 378 } 379 380 } catch (e) { 381 %_Call(deferred.reject, UNDEFINED, e); 382 } 383 return deferred.promise; 384} 385 386function PromiseRace(iterable) { 387 if (!IS_RECEIVER(this)) { 388 throw MakeTypeError(kCalledOnNonObject, PromiseRace); 389 } 390 391 var deferred = NewPromiseCapability(this); 392 try { 393 for (var value of iterable) { 394 this.resolve(value).then(deferred.resolve, deferred.reject); 395 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); 396 } 397 } catch (e) { 398 deferred.reject(e) 399 } 400 return deferred.promise; 401} 402 403 404// Utility for debugger 405 406function PromiseHasUserDefinedRejectHandlerRecursive(promise) { 407 var queue = GET_PRIVATE(promise, promiseOnRejectSymbol); 408 if (IS_UNDEFINED(queue)) return false; 409 for (var i = 0; i < queue.length; i += 2) { 410 var handler = queue[i]; 411 if (handler !== PromiseIdRejectHandler) { 412 var deferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol); 413 if (IS_UNDEFINED(deferred)) return true; 414 if (PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise)) { 415 return true; 416 } 417 } else if (PromiseHasUserDefinedRejectHandlerRecursive( 418 queue[i + 1].promise)) { 419 return true; 420 } 421 } 422 return false; 423} 424 425// Return whether the promise will be handled by a user-defined reject 426// handler somewhere down the promise chain. For this, we do a depth-first 427// search for a reject handler that's not the default PromiseIdRejectHandler. 428function PromiseHasUserDefinedRejectHandler() { 429 return PromiseHasUserDefinedRejectHandlerRecursive(this); 430}; 431 432// ------------------------------------------------------------------- 433// Install exported functions. 434 435%AddNamedProperty(global, 'Promise', GlobalPromise, DONT_ENUM); 436%AddNamedProperty(GlobalPromise.prototype, toStringTagSymbol, "Promise", 437 DONT_ENUM | READ_ONLY); 438 439utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ 440 "reject", PromiseRejected, 441 "all", PromiseAll, 442 "race", PromiseRace, 443 "resolve", PromiseCast 444]); 445 446utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ 447 "then", PromiseThen, 448 "catch", PromiseCatch 449]); 450 451%InstallToContext([ 452 "promise_catch", PromiseCatch, 453 "promise_chain", PromiseChain, 454 "promise_create", PromiseCreate, 455 "promise_has_user_defined_reject_handler", PromiseHasUserDefinedRejectHandler, 456 "promise_reject", PromiseReject, 457 "promise_resolve", PromiseResolve, 458 "promise_then", PromiseThen, 459]); 460 461// This allows extras to create promises quickly without building extra 462// resolve/reject closures, and allows them to later resolve and reject any 463// promise without having to hold on to those closures forever. 464utils.InstallFunctions(extrasUtils, 0, [ 465 "createPromise", PromiseCreate, 466 "resolvePromise", PromiseResolve, 467 "rejectPromise", PromiseReject 468]); 469 470// TODO(v8:4567): Allow experimental natives to remove function prototype 471[PromiseChain, PromiseDeferred, PromiseResolved].forEach( 472 fn => %FunctionRemovePrototype(fn)); 473 474utils.Export(function(to) { 475 to.PromiseChain = PromiseChain; 476 to.PromiseDeferred = PromiseDeferred; 477 to.PromiseResolved = PromiseResolved; 478}); 479 480}) 481