Home | History | Annotate | Download | only in js
      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 MakeTypeError;
     16 var promiseCombinedDeferredSymbol =
     17     utils.ImportNow("promise_combined_deferred_symbol");
     18 var promiseHasHandlerSymbol =
     19     utils.ImportNow("promise_has_handler_symbol");
     20 var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol");
     21 var promiseOnResolveSymbol =
     22     utils.ImportNow("promise_on_resolve_symbol");
     23 var promiseRawSymbol = utils.ImportNow("promise_raw_symbol");
     24 var promiseStatusSymbol = utils.ImportNow("promise_status_symbol");
     25 var promiseValueSymbol = utils.ImportNow("promise_value_symbol");
     26 var SpeciesConstructor;
     27 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
     28 
     29 utils.Import(function(from) {
     30   MakeTypeError = from.MakeTypeError;
     31   SpeciesConstructor = from.SpeciesConstructor;
     32 });
     33 
     34 // -------------------------------------------------------------------
     35 
     36 // Status values: 0 = pending, +1 = resolved, -1 = rejected
     37 var lastMicrotaskId = 0;
     38 
     39 function 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 
     62 var 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 
     87 function 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 
     98 function 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 
    105 function PromiseInit(promise) {
    106   return PromiseSet(
    107       promise, 0, UNDEFINED, new InternalArray, new InternalArray)
    108 }
    109 
    110 function 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 
    118 function 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 
    130 function 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 
    150 function PromiseIdResolveHandler(x) { return x }
    151 function PromiseIdRejectHandler(r) { throw r }
    152 
    153 function PromiseNopResolver() {}
    154 
    155 // -------------------------------------------------------------------
    156 // Define exported functions.
    157 
    158 // For bootstrapper.
    159 
    160 function IsPromise(x) {
    161   return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol);
    162 }
    163 
    164 function PromiseCreate() {
    165   return new GlobalPromise(PromiseNopResolver)
    166 }
    167 
    168 function 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 
    207 function 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 
    222 function 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 
    245 function PromiseDeferred() {
    246   %IncrementUseCounter(kPromiseDefer);
    247   return NewPromiseCapability(this);
    248 }
    249 
    250 function PromiseResolved(x) {
    251   %IncrementUseCounter(kPromiseAccept);
    252   return %_Call(PromiseCast, this, x);
    253 }
    254 
    255 function 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 
    275 function 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
    315 function PromiseChain(onResolve, onReject) {
    316   %IncrementUseCounter(kPromiseChain);
    317   return %_Call(PromiseThen, this, onResolve, onReject);
    318 }
    319 
    320 function PromiseCatch(onReject) {
    321   return this.then(UNDEFINED, onReject);
    322 }
    323 
    324 // Combinators.
    325 
    326 function 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 
    337 function 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 
    386 function 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 
    406 function 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.
    428 function 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 
    439 utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
    440   "reject", PromiseRejected,
    441   "all", PromiseAll,
    442   "race", PromiseRace,
    443   "resolve", PromiseCast
    444 ]);
    445 
    446 utils.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.
    464 utils.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 
    474 utils.Export(function(to) {
    475   to.PromiseChain = PromiseChain;
    476   to.PromiseDeferred = PromiseDeferred;
    477   to.PromiseResolved = PromiseResolved;
    478 });
    479 
    480 })
    481