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