710abadbeb8a0517c5e49dde9646b29361d3d843
[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
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 })();