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 
     22 // mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
     23 // if we could move these property names into the closure below.
     24 // TODO(jkummerow/rossberg/yangguo): Find a better solution.
     25 
     26 // Status values: 0 = pending, +1 = resolved, -1 = rejected
     27 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
     28 var promiseValue = GLOBAL_PRIVATE("Promise#value");
     29 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
     30 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
     31 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
     32 
     33 (function() {
     34 
     35   var $Promise = function Promise(resolver) {
     36     if (resolver === promiseRaw) return;
     37     if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
     38     if (!IS_SPEC_FUNCTION(resolver))
     39       throw MakeTypeError('resolver_not_a_function', [resolver]);
     40     var promise = PromiseInit(this);
     41     try {
     42       %DebugPromiseHandlePrologue(function() { return promise });
     43       resolver(function(x) { PromiseResolve(promise, x) },
     44                function(r) { PromiseReject(promise, r) });
     45     } catch (e) {
     46       PromiseReject(promise, e);
     47     } finally {
     48       %DebugPromiseHandleEpilogue();
     49     }
     50   }
     51 
     52   // Core functionality.
     53 
     54   function PromiseSet(promise, status, value, onResolve, onReject) {
     55     SET_PRIVATE(promise, promiseStatus, status);
     56     SET_PRIVATE(promise, promiseValue, value);
     57     SET_PRIVATE(promise, promiseOnResolve, onResolve);
     58     SET_PRIVATE(promise, promiseOnReject, onReject);
     59     return promise;
     60   }
     61 
     62   function PromiseInit(promise) {
     63     return PromiseSet(
     64         promise, 0, UNDEFINED, new InternalArray, new InternalArray)
     65   }
     66 
     67   function PromiseDone(promise, status, value, promiseQueue) {
     68     if (GET_PRIVATE(promise, promiseStatus) === 0) {
     69       PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
     70       PromiseSet(promise, status, value);
     71     }
     72   }
     73 
     74   function PromiseCoerce(constructor, x) {
     75     if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
     76       var then;
     77       try {
     78         then = x.then;
     79       } catch(r) {
     80         return %_CallFunction(constructor, r, PromiseRejected);
     81       }
     82       if (IS_SPEC_FUNCTION(then)) {
     83         var deferred = %_CallFunction(constructor, PromiseDeferred);
     84         try {
     85           %_CallFunction(x, deferred.resolve, deferred.reject, then);
     86         } catch(r) {
     87           deferred.reject(r);
     88         }
     89         return deferred.promise;
     90       }
     91     }
     92     return x;
     93   }
     94 
     95   function PromiseHandle(value, handler, deferred) {
     96     try {
     97       %DebugPromiseHandlePrologue(
     98           function() {
     99             var queue = GET_PRIVATE(deferred.promise, promiseOnReject);
    100             return (queue && queue.length == 0) ? deferred.promise : UNDEFINED;
    101           });
    102       var result = handler(value);
    103       if (result === deferred.promise)
    104         throw MakeTypeError('promise_cyclic', [result]);
    105       else if (IsPromise(result))
    106         %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
    107       else
    108         deferred.resolve(result);
    109     } catch (exception) {
    110       try {
    111         %DebugPromiseHandlePrologue(function() { return deferred.promise });
    112         deferred.reject(exception);
    113       } catch (e) { } finally {
    114         %DebugPromiseHandleEpilogue();
    115       }
    116     } finally {
    117       %DebugPromiseHandleEpilogue();
    118     }
    119   }
    120 
    121   function PromiseEnqueue(value, tasks) {
    122     %EnqueueMicrotask(function() {
    123       for (var i = 0; i < tasks.length; i += 2) {
    124         PromiseHandle(value, tasks[i], tasks[i + 1])
    125       }
    126     });
    127   }
    128 
    129   function PromiseIdResolveHandler(x) { return x }
    130   function PromiseIdRejectHandler(r) { throw r }
    131 
    132   function PromiseNopResolver() {}
    133 
    134   // -------------------------------------------------------------------
    135   // Define exported functions.
    136 
    137   // For bootstrapper.
    138 
    139   IsPromise = function IsPromise(x) {
    140     return IS_SPEC_OBJECT(x) && HAS_PRIVATE(x, promiseStatus);
    141   }
    142 
    143   PromiseCreate = function PromiseCreate() {
    144     return new $Promise(PromiseNopResolver)
    145   }
    146 
    147   PromiseResolve = function PromiseResolve(promise, x) {
    148     PromiseDone(promise, +1, x, promiseOnResolve)
    149   }
    150 
    151   PromiseReject = function PromiseReject(promise, r) {
    152     PromiseDone(promise, -1, r, promiseOnReject)
    153   }
    154 
    155   // Convenience.
    156 
    157   function PromiseDeferred() {
    158     if (this === $Promise) {
    159       // Optimized case, avoid extra closure.
    160       var promise = PromiseInit(new $Promise(promiseRaw));
    161       return {
    162         promise: promise,
    163         resolve: function(x) { PromiseResolve(promise, x) },
    164         reject: function(r) { PromiseReject(promise, r) }
    165       };
    166     } else {
    167       var result = {};
    168       result.promise = new this(function(resolve, reject) {
    169         result.resolve = resolve;
    170         result.reject = reject;
    171       })
    172       return result;
    173     }
    174   }
    175 
    176   function PromiseResolved(x) {
    177     if (this === $Promise) {
    178       // Optimized case, avoid extra closure.
    179       return PromiseSet(new $Promise(promiseRaw), +1, x);
    180     } else {
    181       return new this(function(resolve, reject) { resolve(x) });
    182     }
    183   }
    184 
    185   function PromiseRejected(r) {
    186     if (this === $Promise) {
    187       // Optimized case, avoid extra closure.
    188       return PromiseSet(new $Promise(promiseRaw), -1, r);
    189     } else {
    190       return new this(function(resolve, reject) { reject(r) });
    191     }
    192   }
    193 
    194   // Simple chaining.
    195 
    196   PromiseChain = function PromiseChain(onResolve, onReject) {  // a.k.a.
    197                                                                 // flatMap
    198     onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
    199     onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
    200     var deferred = %_CallFunction(this.constructor, PromiseDeferred);
    201     switch (GET_PRIVATE(this, promiseStatus)) {
    202       case UNDEFINED:
    203         throw MakeTypeError('not_a_promise', [this]);
    204       case 0:  // Pending
    205         GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
    206         GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
    207         break;
    208       case +1:  // Resolved
    209         PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
    210         break;
    211       case -1:  // Rejected
    212         PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
    213         break;
    214     }
    215     return deferred.promise;
    216   }
    217 
    218   PromiseCatch = function PromiseCatch(onReject) {
    219     return this.then(UNDEFINED, onReject);
    220   }
    221 
    222   // Multi-unwrapped chaining with thenable coercion.
    223 
    224   PromiseThen = function PromiseThen(onResolve, onReject) {
    225     onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
    226                                             : PromiseIdResolveHandler;
    227     onReject = IS_SPEC_FUNCTION(onReject) ? onReject
    228                                           : PromiseIdRejectHandler;
    229     var that = this;
    230     var constructor = this.constructor;
    231     return %_CallFunction(
    232       this,
    233       function(x) {
    234         x = PromiseCoerce(constructor, x);
    235         return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
    236                IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
    237       },
    238       onReject,
    239       PromiseChain
    240     );
    241   }
    242 
    243   // Combinators.
    244 
    245   function PromiseCast(x) {
    246     // TODO(rossberg): cannot do better until we support @@create.
    247     return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
    248   }
    249 
    250   function PromiseAll(values) {
    251     var deferred = %_CallFunction(this, PromiseDeferred);
    252     var resolutions = [];
    253     if (!%_IsArray(values)) {
    254       deferred.reject(MakeTypeError('invalid_argument'));
    255       return deferred.promise;
    256     }
    257     try {
    258       var count = values.length;
    259       if (count === 0) {
    260         deferred.resolve(resolutions);
    261       } else {
    262         for (var i = 0; i < values.length; ++i) {
    263           this.resolve(values[i]).then(
    264             function(i, x) {
    265               resolutions[i] = x;
    266               if (--count === 0) deferred.resolve(resolutions);
    267             }.bind(UNDEFINED, i),  // TODO(rossberg): use let loop once
    268                                     // available
    269             function(r) { deferred.reject(r) }
    270           );
    271         }
    272       }
    273     } catch (e) {
    274       deferred.reject(e)
    275     }
    276     return deferred.promise;
    277   }
    278 
    279   function PromiseOne(values) {
    280     var deferred = %_CallFunction(this, PromiseDeferred);
    281     if (!%_IsArray(values)) {
    282       deferred.reject(MakeTypeError('invalid_argument'));
    283       return deferred.promise;
    284     }
    285     try {
    286       for (var i = 0; i < values.length; ++i) {
    287         this.resolve(values[i]).then(
    288           function(x) { deferred.resolve(x) },
    289           function(r) { deferred.reject(r) }
    290         );
    291       }
    292     } catch (e) {
    293       deferred.reject(e)
    294     }
    295     return deferred.promise;
    296   }
    297 
    298   // -------------------------------------------------------------------
    299   // Install exported functions.
    300 
    301   %CheckIsBootstrapping();
    302   %SetProperty(global, 'Promise', $Promise, DONT_ENUM);
    303   InstallFunctions($Promise, DONT_ENUM, [
    304     "defer", PromiseDeferred,
    305     "accept", PromiseResolved,
    306     "reject", PromiseRejected,
    307     "all", PromiseAll,
    308     "race", PromiseOne,
    309     "resolve", PromiseCast
    310   ]);
    311   InstallFunctions($Promise.prototype, DONT_ENUM, [
    312     "chain", PromiseChain,
    313     "then", PromiseThen,
    314     "catch", PromiseCatch
    315   ]);
    316 
    317 })();
    318