Home | History | Annotate | Download | only in src
      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 "use strict";
      6 
      7 // This file relies on the fact that the following declaration has been made
      8 // in runtime.js:
      9 // var $Object = global.Object
     10 // var $WeakMap = global.WeakMap
     11 
     12 // For bootstrapper.
     13 
     14 var IsPromise;
     15 var PromiseCreate;
     16 var PromiseResolve;
     17 var PromiseReject;
     18 var PromiseChain;
     19 var PromiseCatch;
     20 var PromiseThen;
     21 var PromiseHasRejectHandler;
     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 
     27 // Status values: 0 = pending, +1 = resolved, -1 = rejected
     28 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
     29 var promiseValue = GLOBAL_PRIVATE("Promise#value");
     30 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
     31 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
     32 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
     33 var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
     34 var lastMicrotaskId = 0;
     35 
     36 (function() {
     37 
     38   var $Promise = function Promise(resolver) {
     39     if (resolver === promiseRaw) return;
     40     if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
     41     if (!IS_SPEC_FUNCTION(resolver))
     42       throw MakeTypeError('resolver_not_a_function', [resolver]);
     43     var promise = PromiseInit(this);
     44     try {
     45       %DebugPushPromise(promise);
     46       resolver(function(x) { PromiseResolve(promise, x) },
     47                function(r) { PromiseReject(promise, r) });
     48     } catch (e) {
     49       PromiseReject(promise, e);
     50     } finally {
     51       %DebugPopPromise();
     52     }
     53   }
     54 
     55   // Core functionality.
     56 
     57   function PromiseSet(promise, status, value, onResolve, onReject) {
     58     SET_PRIVATE(promise, promiseStatus, status);
     59     SET_PRIVATE(promise, promiseValue, value);
     60     SET_PRIVATE(promise, promiseOnResolve, onResolve);
     61     SET_PRIVATE(promise, promiseOnReject, onReject);
     62     if (DEBUG_IS_ACTIVE) {
     63       %DebugPromiseEvent({ promise: promise, status: status, value: value });
     64     }
     65     return promise;
     66   }
     67 
     68   function PromiseInit(promise) {
     69     return PromiseSet(
     70         promise, 0, UNDEFINED, new InternalArray, new InternalArray)
     71   }
     72 
     73   function PromiseDone(promise, status, value, promiseQueue) {
     74     if (GET_PRIVATE(promise, promiseStatus) === 0) {
     75       PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), 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_SPEC_FUNCTION(then)) {
     89         var deferred = %_CallFunction(constructor, PromiseDeferred);
     90         try {
     91           %_CallFunction(x, deferred.resolve, deferred.reject, then);
     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);
    104       var result = handler(value);
    105       if (result === deferred.promise)
    106         throw MakeTypeError('promise_cyclic', [result]);
    107       else if (IsPromise(result))
    108         %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
    109       else
    110         deferred.resolve(result);
    111     } catch (exception) {
    112       try { deferred.reject(exception); } catch (e) { }
    113     } finally {
    114       %DebugPopPromise();
    115     }
    116   }
    117 
    118   function PromiseEnqueue(value, tasks, status) {
    119     var id, name, instrumenting = DEBUG_IS_ACTIVE;
    120     %EnqueueMicrotask(function() {
    121       if (instrumenting) {
    122         %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
    123       }
    124       for (var i = 0; i < tasks.length; i += 2) {
    125         PromiseHandle(value, tasks[i], tasks[i + 1])
    126       }
    127       if (instrumenting) {
    128         %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
    129       }
    130     });
    131     if (instrumenting) {
    132       id = ++lastMicrotaskId;
    133       name = status > 0 ? "Promise.resolve" : "Promise.reject";
    134       %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
    135     }
    136   }
    137 
    138   function PromiseIdResolveHandler(x) { return x }
    139   function PromiseIdRejectHandler(r) { throw r }
    140 
    141   function PromiseNopResolver() {}
    142 
    143   // -------------------------------------------------------------------
    144   // Define exported functions.
    145 
    146   // For bootstrapper.
    147 
    148   IsPromise = function IsPromise(x) {
    149     return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus);
    150   }
    151 
    152   PromiseCreate = function PromiseCreate() {
    153     return new $Promise(PromiseNopResolver)
    154   }
    155 
    156   PromiseResolve = function PromiseResolve(promise, x) {
    157     PromiseDone(promise, +1, x, promiseOnResolve)
    158   }
    159 
    160   PromiseReject = function PromiseReject(promise, r) {
    161     // Check promise status to confirm that this reject has an effect.
    162     // Check promiseDebug property to avoid duplicate event.
    163     if (DEBUG_IS_ACTIVE &&
    164         GET_PRIVATE(promise, promiseStatus) == 0 &&
    165         !HAS_DEFINED_PRIVATE(promise, promiseDebug)) {
    166       %DebugPromiseRejectEvent(promise, r);
    167     }
    168     PromiseDone(promise, -1, r, promiseOnReject)
    169   }
    170 
    171   // Convenience.
    172 
    173   function PromiseDeferred() {
    174     if (this === $Promise) {
    175       // Optimized case, avoid extra closure.
    176       var promise = PromiseInit(new $Promise(promiseRaw));
    177       return {
    178         promise: promise,
    179         resolve: function(x) { PromiseResolve(promise, x) },
    180         reject: function(r) { PromiseReject(promise, r) }
    181       };
    182     } else {
    183       var result = {};
    184       result.promise = new this(function(resolve, reject) {
    185         result.resolve = resolve;
    186         result.reject = reject;
    187       })
    188       return result;
    189     }
    190   }
    191 
    192   function PromiseResolved(x) {
    193     if (this === $Promise) {
    194       // Optimized case, avoid extra closure.
    195       return PromiseSet(new $Promise(promiseRaw), +1, x);
    196     } else {
    197       return new this(function(resolve, reject) { resolve(x) });
    198     }
    199   }
    200 
    201   function PromiseRejected(r) {
    202     if (this === $Promise) {
    203       // Optimized case, avoid extra closure.
    204       return PromiseSet(new $Promise(promiseRaw), -1, r);
    205     } else {
    206       return new this(function(resolve, reject) { reject(r) });
    207     }
    208   }
    209 
    210   // Simple chaining.
    211 
    212   PromiseChain = function PromiseChain(onResolve, onReject) {  // a.k.a.
    213                                                                // flatMap
    214     onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
    215     onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
    216     var deferred = %_CallFunction(this.constructor, PromiseDeferred);
    217     switch (GET_PRIVATE(this, promiseStatus)) {
    218       case UNDEFINED:
    219         throw MakeTypeError('not_a_promise', [this]);
    220       case 0:  // Pending
    221         GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
    222         GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
    223         break;
    224       case +1:  // Resolved
    225         PromiseEnqueue(GET_PRIVATE(this, promiseValue),
    226                        [onResolve, deferred],
    227                        +1);
    228         break;
    229       case -1:  // Rejected
    230         PromiseEnqueue(GET_PRIVATE(this, promiseValue),
    231                        [onReject, deferred],
    232                        -1);
    233         break;
    234     }
    235     if (DEBUG_IS_ACTIVE) {
    236       %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
    237     }
    238     return deferred.promise;
    239   }
    240 
    241   PromiseCatch = function PromiseCatch(onReject) {
    242     return this.then(UNDEFINED, onReject);
    243   }
    244 
    245   // Multi-unwrapped chaining with thenable coercion.
    246 
    247   PromiseThen = function PromiseThen(onResolve, onReject) {
    248     onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
    249                                             : PromiseIdResolveHandler;
    250     onReject = IS_SPEC_FUNCTION(onReject) ? onReject
    251                                           : PromiseIdRejectHandler;
    252     var that = this;
    253     var constructor = this.constructor;
    254     return %_CallFunction(
    255       this,
    256       function(x) {
    257         x = PromiseCoerce(constructor, x);
    258         return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
    259                IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
    260       },
    261       onReject,
    262       PromiseChain
    263     );
    264   }
    265 
    266   // Combinators.
    267 
    268   function PromiseCast(x) {
    269     // TODO(rossberg): cannot do better until we support @@create.
    270     return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
    271   }
    272 
    273   function PromiseAll(values) {
    274     var deferred = %_CallFunction(this, PromiseDeferred);
    275     var resolutions = [];
    276     if (!%_IsArray(values)) {
    277       deferred.reject(MakeTypeError('invalid_argument'));
    278       return deferred.promise;
    279     }
    280     try {
    281       var count = values.length;
    282       if (count === 0) {
    283         deferred.resolve(resolutions);
    284       } else {
    285         for (var i = 0; i < values.length; ++i) {
    286           this.resolve(values[i]).then(
    287             (function() {
    288               // Nested scope to get closure over current i (and avoid .bind).
    289               // TODO(rossberg): Use for-let instead once available.
    290               var i_captured = i;
    291               return function(x) {
    292                 resolutions[i_captured] = x;
    293                 if (--count === 0) deferred.resolve(resolutions);
    294               };
    295             })(),
    296             function(r) { deferred.reject(r) }
    297           );
    298         }
    299       }
    300     } catch (e) {
    301       deferred.reject(e)
    302     }
    303     return deferred.promise;
    304   }
    305 
    306   function PromiseOne(values) {
    307     var deferred = %_CallFunction(this, PromiseDeferred);
    308     if (!%_IsArray(values)) {
    309       deferred.reject(MakeTypeError('invalid_argument'));
    310       return deferred.promise;
    311     }
    312     try {
    313       for (var i = 0; i < values.length; ++i) {
    314         this.resolve(values[i]).then(
    315           function(x) { deferred.resolve(x) },
    316           function(r) { deferred.reject(r) }
    317         );
    318       }
    319     } catch (e) {
    320       deferred.reject(e)
    321     }
    322     return deferred.promise;
    323   }
    324 
    325 
    326   // Utility for debugger
    327 
    328   function PromiseHasRejectHandlerRecursive(promise) {
    329     var queue = GET_PRIVATE(promise, promiseOnReject);
    330     if (IS_UNDEFINED(queue)) return false;
    331     // Do a depth first search for a reject handler that's not
    332     // the default PromiseIdRejectHandler.
    333     for (var i = 0; i < queue.length; i += 2) {
    334       if (queue[i] != PromiseIdRejectHandler) return true;
    335       if (PromiseHasRejectHandlerRecursive(queue[i + 1].promise)) return true;
    336     }
    337     return false;
    338   }
    339 
    340   PromiseHasRejectHandler = function PromiseHasRejectHandler() {
    341     // Mark promise as already having triggered a reject event.
    342     SET_PRIVATE(this, promiseDebug, true);
    343     return PromiseHasRejectHandlerRecursive(this);
    344   };
    345 
    346   // -------------------------------------------------------------------
    347   // Install exported functions.
    348 
    349   %CheckIsBootstrapping();
    350   %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM);
    351   InstallFunctions($Promise, DONT_ENUM, [
    352     "defer", PromiseDeferred,
    353     "accept", PromiseResolved,
    354     "reject", PromiseRejected,
    355     "all", PromiseAll,
    356     "race", PromiseOne,
    357     "resolve", PromiseCast
    358   ]);
    359   InstallFunctions($Promise.prototype, DONT_ENUM, [
    360     "chain", PromiseChain,
    361     "then", PromiseThen,
    362     "catch", PromiseCatch
    363   ]);
    364 
    365 })();
    366