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