Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / v8 / src / promise.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 "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_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_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   PromiseHasRejectHandler = function PromiseHasRejectHandler() {
329     // Mark promise as already having triggered a reject event.
330     SET_PRIVATE(this, promiseDebug, true);
331     var queue = GET_PRIVATE(this, promiseOnReject);
332     return !IS_UNDEFINED(queue) && queue.length > 0;
333   };
334
335   // -------------------------------------------------------------------
336   // Install exported functions.
337
338   %CheckIsBootstrapping();
339   %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM);
340   InstallFunctions($Promise, DONT_ENUM, [
341     "defer", PromiseDeferred,
342     "accept", PromiseResolved,
343     "reject", PromiseRejected,
344     "all", PromiseAll,
345     "race", PromiseOne,
346     "resolve", PromiseCast
347   ]);
348   InstallFunctions($Promise.prototype, DONT_ENUM, [
349     "chain", PromiseChain,
350     "then", PromiseThen,
351     "catch", PromiseCatch
352   ]);
353
354 })();