Home | History | Annotate | Download | only in src
      1 // Copyright 2012 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 
     29 "use strict";
     30 
     31 // This file relies on the fact that the following declaration has been made
     32 // in runtime.js:
     33 // var $Object = global.Object
     34 // var $WeakMap = global.WeakMap
     35 
     36 
     37 var $Promise = Promise;
     38 
     39 
     40 //-------------------------------------------------------------------
     41 
     42 // Core functionality.
     43 
     44 // Event queue format: [(value, [(handler, deferred)*])*]
     45 // I.e., a list of value/tasks pairs, where the value is a resolution value or
     46 // rejection reason, and the tasks are a respective list of handler/deferred
     47 // pairs waiting for notification of this value. Each handler is an onResolve or
     48 // onReject function provided to the same call of 'chain' that produced the
     49 // associated deferred.
     50 var promiseEvents = new InternalArray;
     51 
     52 // Status values: 0 = pending, +1 = resolved, -1 = rejected
     53 var promiseStatus = NEW_PRIVATE("Promise#status");
     54 var promiseValue = NEW_PRIVATE("Promise#value");
     55 var promiseOnResolve = NEW_PRIVATE("Promise#onResolve");
     56 var promiseOnReject = NEW_PRIVATE("Promise#onReject");
     57 var promiseRaw = NEW_PRIVATE("Promise#raw");
     58 
     59 function IsPromise(x) {
     60   return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus);
     61 }
     62 
     63 function Promise(resolver) {
     64   if (resolver === promiseRaw) return;
     65   var promise = PromiseInit(this);
     66   resolver(function(x) { PromiseResolve(promise, x) },
     67            function(r) { PromiseReject(promise, r) });
     68   // TODO(rossberg): current draft makes exception from this call asynchronous,
     69   // but that's probably a mistake.
     70 }
     71 
     72 function PromiseSet(promise, status, value, onResolve, onReject) {
     73   SET_PRIVATE(promise, promiseStatus, status);
     74   SET_PRIVATE(promise, promiseValue, value);
     75   SET_PRIVATE(promise, promiseOnResolve, onResolve);
     76   SET_PRIVATE(promise, promiseOnReject, onReject);
     77   return promise;
     78 }
     79 
     80 function PromiseInit(promise) {
     81   return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray)
     82 }
     83 
     84 function PromiseDone(promise, status, value, promiseQueue) {
     85   if (GET_PRIVATE(promise, promiseStatus) !== 0) return;
     86   PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
     87   PromiseSet(promise, status, value);
     88 }
     89 
     90 function PromiseResolve(promise, x) {
     91   PromiseDone(promise, +1, x, promiseOnResolve)
     92 }
     93 
     94 function PromiseReject(promise, r) {
     95   PromiseDone(promise, -1, r, promiseOnReject)
     96 }
     97 
     98 
     99 // Convenience.
    100 
    101 function PromiseDeferred() {
    102   if (this === $Promise) {
    103     // Optimized case, avoid extra closure.
    104     var promise = PromiseInit(new Promise(promiseRaw));
    105     return {
    106       promise: promise,
    107       resolve: function(x) { PromiseResolve(promise, x) },
    108       reject: function(r) { PromiseReject(promise, r) }
    109     };
    110   } else {
    111     var result = {};
    112     result.promise = new this(function(resolve, reject) {
    113       result.resolve = resolve;
    114       result.reject = reject;
    115     })
    116     return result;
    117   }
    118 }
    119 
    120 function PromiseResolved(x) {
    121   if (this === $Promise) {
    122     // Optimized case, avoid extra closure.
    123     return PromiseSet(new Promise(promiseRaw), +1, x);
    124   } else {
    125     return new this(function(resolve, reject) { resolve(x) });
    126   }
    127 }
    128 
    129 function PromiseRejected(r) {
    130   if (this === $Promise) {
    131     // Optimized case, avoid extra closure.
    132     return PromiseSet(new Promise(promiseRaw), -1, r);
    133   } else {
    134     return new this(function(resolve, reject) { reject(r) });
    135   }
    136 }
    137 
    138 
    139 // Simple chaining.
    140 
    141 function PromiseIdResolveHandler(x) { return x }
    142 function PromiseIdRejectHandler(r) { throw r }
    143 
    144 function PromiseChain(onResolve, onReject) {  // a.k.a.  flatMap
    145   onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
    146   onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
    147   var deferred = %_CallFunction(this.constructor, PromiseDeferred);
    148   switch (GET_PRIVATE(this, promiseStatus)) {
    149     case UNDEFINED:
    150       throw MakeTypeError('not_a_promise', [this]);
    151     case 0:  // Pending
    152       GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
    153       GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
    154       break;
    155     case +1:  // Resolved
    156       PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
    157       break;
    158     case -1:  // Rejected
    159       PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
    160       break;
    161   }
    162   return deferred.promise;
    163 }
    164 
    165 function PromiseCatch(onReject) {
    166   return this.chain(UNDEFINED, onReject);
    167 }
    168 
    169 function PromiseEnqueue(value, tasks) {
    170   promiseEvents.push(value, tasks);
    171   %SetMicrotaskPending(true);
    172 }
    173 
    174 function PromiseMicrotaskRunner() {
    175   var events = promiseEvents;
    176   if (events.length > 0) {
    177     promiseEvents = new InternalArray;
    178     for (var i = 0; i < events.length; i += 2) {
    179       var value = events[i];
    180       var tasks = events[i + 1];
    181       for (var j = 0; j < tasks.length; j += 2) {
    182         var handler = tasks[j];
    183         var deferred = tasks[j + 1];
    184         try {
    185           var result = handler(value);
    186           if (result === deferred.promise)
    187             throw MakeTypeError('promise_cyclic', [result]);
    188           else if (IsPromise(result))
    189             result.chain(deferred.resolve, deferred.reject);
    190           else
    191             deferred.resolve(result);
    192         } catch(e) {
    193           // TODO(rossberg): perhaps log uncaught exceptions below.
    194           try { deferred.reject(e) } catch(e) {}
    195         }
    196       }
    197     }
    198   }
    199 }
    200 RunMicrotasks.runners.push(PromiseMicrotaskRunner);
    201 
    202 
    203 // Multi-unwrapped chaining with thenable coercion.
    204 
    205 function PromiseThen(onResolve, onReject) {
    206   onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
    207   var that = this;
    208   var constructor = this.constructor;
    209   return this.chain(
    210     function(x) {
    211       x = PromiseCoerce(constructor, x);
    212       return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
    213              IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
    214     },
    215     onReject
    216   );
    217 }
    218 
    219 PromiseCoerce.table = new $WeakMap;
    220 
    221 function PromiseCoerce(constructor, x) {
    222   var then;
    223   if (IsPromise(x)) {
    224     return x;
    225   } else if (!IS_NULL_OR_UNDEFINED(x) && %IsCallable(then = x.then)) {
    226     if (PromiseCoerce.table.has(x)) {
    227       return PromiseCoerce.table.get(x);
    228     } else {
    229       var deferred = constructor.deferred();
    230       PromiseCoerce.table.set(x, deferred.promise);
    231       try {
    232         %_CallFunction(x, deferred.resolve, deferred.reject, then);
    233       } catch(e) {
    234         deferred.reject(e);
    235       }
    236       return deferred.promise;
    237     }
    238   } else {
    239     return x;
    240   }
    241 }
    242 
    243 
    244 // Combinators.
    245 
    246 function PromiseCast(x) {
    247   // TODO(rossberg): cannot do better until we support @@create.
    248   return IsPromise(x) ? x : this.resolved(x);
    249 }
    250 
    251 function PromiseAll(values) {
    252   var deferred = this.deferred();
    253   var resolutions = [];
    254   var count = values.length;
    255   if (count === 0) {
    256     deferred.resolve(resolutions);
    257   } else {
    258     for (var i = 0; i < values.length; ++i) {
    259       this.cast(values[i]).chain(
    260         function(i, x) {
    261           resolutions[i] = x;
    262           if (--count === 0) deferred.resolve(resolutions);
    263         }.bind(UNDEFINED, i),  // TODO(rossberg): use let loop once available
    264         function(r) {
    265           if (count > 0) { count = 0; deferred.reject(r) }
    266         }
    267       );
    268     }
    269   }
    270   return deferred.promise;
    271 }
    272 
    273 function PromiseOne(values) {  // a.k.a. race
    274   var deferred = this.deferred();
    275   var done = false;
    276   for (var i = 0; i < values.length; ++i) {
    277     this.cast(values[i]).chain(
    278       function(x) { if (!done) { done = true; deferred.resolve(x) } },
    279       function(r) { if (!done) { done = true; deferred.reject(r) } }
    280     );
    281   }
    282   return deferred.promise;
    283 }
    284 
    285 //-------------------------------------------------------------------
    286 
    287 function SetUpPromise() {
    288   %CheckIsBootstrapping()
    289   global.Promise = $Promise;
    290   InstallFunctions($Promise, DONT_ENUM, [
    291     "deferred", PromiseDeferred,
    292     "resolved", PromiseResolved,
    293     "rejected", PromiseRejected,
    294     "all", PromiseAll,
    295     "one", PromiseOne,
    296     "cast", PromiseCast
    297   ]);
    298   InstallFunctions($Promise.prototype, DONT_ENUM, [
    299     "chain", PromiseChain,
    300     "then", PromiseThen,
    301     "catch", PromiseCatch
    302   ]);
    303 }
    304 
    305 SetUpPromise();
    306