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