• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
14var IsPromise;
15var PromiseCreate;
16var PromiseResolve;
17var PromiseReject;
18var PromiseChain;
19var PromiseCatch;
20var 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
27var promiseStatus = GLOBAL_PRIVATE("Promise#status");
28var promiseValue = GLOBAL_PRIVATE("Promise#value");
29var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
30var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
31var 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