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 var $promiseCreate; |
| 6 var $promiseResolve; |
| 7 var $promiseReject; |
| 8 var $promiseChain; |
| 9 var $promiseCatch; |
| 10 var $promiseThen; |
| 11 var $promiseHasUserDefinedRejectHandler; |
| 12 var $promiseStatus; |
| 13 var $promiseValue; |
| 14 |
| 15 (function() { |
| 16 |
5 "use strict"; | 17 "use strict"; |
6 | 18 |
7 // This file relies on the fact that the following declaration has been made | 19 %CheckIsBootstrapping(); |
8 // in runtime.js: | |
9 // var $Object = global.Object | |
10 | 20 |
11 // For bootstrapper. | 21 // ------------------------------------------------------------------- |
12 | |
13 var IsPromise; | |
14 var PromiseCreate; | |
15 var PromiseResolve; | |
16 var PromiseReject; | |
17 var PromiseChain; | |
18 var PromiseCatch; | |
19 var PromiseThen; | |
20 var PromiseHasRejectHandler; | |
21 var PromiseHasUserDefinedRejectHandler; | |
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 | 22 |
27 // Status values: 0 = pending, +1 = resolved, -1 = rejected | 23 // Status values: 0 = pending, +1 = resolved, -1 = rejected |
28 var promiseStatus = GLOBAL_PRIVATE("Promise#status"); | 24 var promiseStatus = GLOBAL_PRIVATE("Promise#status"); |
29 var promiseValue = GLOBAL_PRIVATE("Promise#value"); | 25 var promiseValue = GLOBAL_PRIVATE("Promise#value"); |
30 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); | 26 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve"); |
31 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); | 27 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject"); |
32 var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); | 28 var promiseRaw = GLOBAL_PRIVATE("Promise#raw"); |
33 var promiseHasHandler = %PromiseHasHandlerSymbol(); | 29 var promiseHasHandler = %PromiseHasHandlerSymbol(); |
34 var lastMicrotaskId = 0; | 30 var lastMicrotaskId = 0; |
35 | 31 |
36 | 32 var GlobalPromise = function Promise(resolver) { |
37 (function() { | 33 if (resolver === promiseRaw) return; |
38 | 34 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]); |
39 var $Promise = function Promise(resolver) { | 35 if (!IS_SPEC_FUNCTION(resolver)) |
40 if (resolver === promiseRaw) return; | 36 throw MakeTypeError('resolver_not_a_function', [resolver]); |
41 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]); | 37 var promise = PromiseInit(this); |
42 if (!IS_SPEC_FUNCTION(resolver)) | 38 try { |
43 throw MakeTypeError('resolver_not_a_function', [resolver]); | 39 %DebugPushPromise(promise, Promise); |
44 var promise = PromiseInit(this); | 40 resolver(function(x) { PromiseResolve(promise, x) }, |
| 41 function(r) { PromiseReject(promise, r) }); |
| 42 } catch (e) { |
| 43 PromiseReject(promise, e); |
| 44 } finally { |
| 45 %DebugPopPromise(); |
| 46 } |
| 47 } |
| 48 |
| 49 // Core functionality. |
| 50 |
| 51 function PromiseSet(promise, status, value, onResolve, onReject) { |
| 52 SET_PRIVATE(promise, promiseStatus, status); |
| 53 SET_PRIVATE(promise, promiseValue, value); |
| 54 SET_PRIVATE(promise, promiseOnResolve, onResolve); |
| 55 SET_PRIVATE(promise, promiseOnReject, onReject); |
| 56 if (DEBUG_IS_ACTIVE) { |
| 57 %DebugPromiseEvent({ promise: promise, status: status, value: value }); |
| 58 } |
| 59 return promise; |
| 60 } |
| 61 |
| 62 function PromiseCreateAndSet(status, value) { |
| 63 var promise = new GlobalPromise(promiseRaw); |
| 64 // If debug is active, notify about the newly created promise first. |
| 65 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED); |
| 66 return PromiseSet(promise, status, value); |
| 67 } |
| 68 |
| 69 function PromiseInit(promise) { |
| 70 return PromiseSet( |
| 71 promise, 0, UNDEFINED, new InternalArray, new InternalArray) |
| 72 } |
| 73 |
| 74 function PromiseDone(promise, status, value, promiseQueue) { |
| 75 if (GET_PRIVATE(promise, promiseStatus) === 0) { |
| 76 var tasks = GET_PRIVATE(promise, promiseQueue); |
| 77 if (tasks.length) PromiseEnqueue(value, tasks, status); |
| 78 PromiseSet(promise, status, value); |
| 79 } |
| 80 } |
| 81 |
| 82 function PromiseCoerce(constructor, x) { |
| 83 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { |
| 84 var then; |
45 try { | 85 try { |
46 %DebugPushPromise(promise, Promise); | 86 then = x.then; |
47 resolver(function(x) { PromiseResolve(promise, x) }, | 87 } catch(r) { |
48 function(r) { PromiseReject(promise, r) }); | 88 return %_CallFunction(constructor, r, PromiseRejected); |
49 } catch (e) { | 89 } |
50 PromiseReject(promise, e); | 90 if (IS_SPEC_FUNCTION(then)) { |
51 } finally { | 91 var deferred = %_CallFunction(constructor, PromiseDeferred); |
52 %DebugPopPromise(); | |
53 } | |
54 } | |
55 | |
56 // Core functionality. | |
57 | |
58 function PromiseSet(promise, status, value, onResolve, onReject) { | |
59 SET_PRIVATE(promise, promiseStatus, status); | |
60 SET_PRIVATE(promise, promiseValue, value); | |
61 SET_PRIVATE(promise, promiseOnResolve, onResolve); | |
62 SET_PRIVATE(promise, promiseOnReject, onReject); | |
63 if (DEBUG_IS_ACTIVE) { | |
64 %DebugPromiseEvent({ promise: promise, status: status, value: value }); | |
65 } | |
66 return promise; | |
67 } | |
68 | |
69 function PromiseCreateAndSet(status, value) { | |
70 var promise = new $Promise(promiseRaw); | |
71 // If debug is active, notify about the newly created promise first. | |
72 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED); | |
73 return PromiseSet(promise, status, value); | |
74 } | |
75 | |
76 function PromiseInit(promise) { | |
77 return PromiseSet( | |
78 promise, 0, UNDEFINED, new InternalArray, new InternalArray) | |
79 } | |
80 | |
81 function PromiseDone(promise, status, value, promiseQueue) { | |
82 if (GET_PRIVATE(promise, promiseStatus) === 0) { | |
83 var tasks = GET_PRIVATE(promise, promiseQueue); | |
84 if (tasks.length) PromiseEnqueue(value, tasks, status); | |
85 PromiseSet(promise, status, value); | |
86 } | |
87 } | |
88 | |
89 function PromiseCoerce(constructor, x) { | |
90 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) { | |
91 var then; | |
92 try { | 92 try { |
93 then = x.then; | 93 %_CallFunction(x, deferred.resolve, deferred.reject, then); |
94 } catch(r) { | 94 } catch(r) { |
95 return %_CallFunction(constructor, r, PromiseRejected); | 95 deferred.reject(r); |
96 } | 96 } |
97 if (IS_SPEC_FUNCTION(then)) { | 97 return deferred.promise; |
98 var deferred = %_CallFunction(constructor, PromiseDeferred); | 98 } |
99 try { | 99 } |
100 %_CallFunction(x, deferred.resolve, deferred.reject, then); | 100 return x; |
101 } catch(r) { | 101 } |
102 deferred.reject(r); | 102 |
103 } | 103 function PromiseHandle(value, handler, deferred) { |
104 return deferred.promise; | 104 try { |
| 105 %DebugPushPromise(deferred.promise, PromiseHandle); |
| 106 DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler); |
| 107 var result = handler(value); |
| 108 if (result === deferred.promise) |
| 109 throw MakeTypeError('promise_cyclic', [result]); |
| 110 else if (IsPromise(result)) |
| 111 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain); |
| 112 else |
| 113 deferred.resolve(result); |
| 114 } catch (exception) { |
| 115 try { deferred.reject(exception); } catch (e) { } |
| 116 } finally { |
| 117 %DebugPopPromise(); |
| 118 } |
| 119 } |
| 120 |
| 121 function PromiseEnqueue(value, tasks, status) { |
| 122 var id, name, instrumenting = DEBUG_IS_ACTIVE; |
| 123 %EnqueueMicrotask(function() { |
| 124 if (instrumenting) { |
| 125 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); |
| 126 } |
| 127 for (var i = 0; i < tasks.length; i += 2) { |
| 128 PromiseHandle(value, tasks[i], tasks[i + 1]) |
| 129 } |
| 130 if (instrumenting) { |
| 131 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); |
| 132 } |
| 133 }); |
| 134 if (instrumenting) { |
| 135 id = ++lastMicrotaskId; |
| 136 name = status > 0 ? "Promise.resolve" : "Promise.reject"; |
| 137 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); |
| 138 } |
| 139 } |
| 140 |
| 141 function PromiseIdResolveHandler(x) { return x } |
| 142 function PromiseIdRejectHandler(r) { throw r } |
| 143 |
| 144 function PromiseNopResolver() {} |
| 145 |
| 146 // ------------------------------------------------------------------- |
| 147 // Define exported functions. |
| 148 |
| 149 // For bootstrapper. |
| 150 |
| 151 function IsPromise(x) { |
| 152 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus); |
| 153 } |
| 154 |
| 155 function PromiseCreate() { |
| 156 return new GlobalPromise(PromiseNopResolver) |
| 157 } |
| 158 |
| 159 function PromiseResolve(promise, x) { |
| 160 PromiseDone(promise, +1, x, promiseOnResolve) |
| 161 } |
| 162 |
| 163 function PromiseReject(promise, r) { |
| 164 // Check promise status to confirm that this reject has an effect. |
| 165 // Call runtime for callbacks to the debugger or for unhandled reject. |
| 166 if (GET_PRIVATE(promise, promiseStatus) == 0) { |
| 167 var debug_is_active = DEBUG_IS_ACTIVE; |
| 168 if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) { |
| 169 %PromiseRejectEvent(promise, r, debug_is_active); |
| 170 } |
| 171 } |
| 172 PromiseDone(promise, -1, r, promiseOnReject) |
| 173 } |
| 174 |
| 175 // Convenience. |
| 176 |
| 177 function PromiseDeferred() { |
| 178 if (this === GlobalPromise) { |
| 179 // Optimized case, avoid extra closure. |
| 180 var promise = PromiseInit(new GlobalPromise(promiseRaw)); |
| 181 return { |
| 182 promise: promise, |
| 183 resolve: function(x) { PromiseResolve(promise, x) }, |
| 184 reject: function(r) { PromiseReject(promise, r) } |
| 185 }; |
| 186 } else { |
| 187 var result = {}; |
| 188 result.promise = new this(function(resolve, reject) { |
| 189 result.resolve = resolve; |
| 190 result.reject = reject; |
| 191 }) |
| 192 return result; |
| 193 } |
| 194 } |
| 195 |
| 196 function PromiseResolved(x) { |
| 197 if (this === GlobalPromise) { |
| 198 // Optimized case, avoid extra closure. |
| 199 return PromiseCreateAndSet(+1, x); |
| 200 } else { |
| 201 return new this(function(resolve, reject) { resolve(x) }); |
| 202 } |
| 203 } |
| 204 |
| 205 function PromiseRejected(r) { |
| 206 var promise; |
| 207 if (this === GlobalPromise) { |
| 208 // Optimized case, avoid extra closure. |
| 209 promise = PromiseCreateAndSet(-1, r); |
| 210 // The debug event for this would always be an uncaught promise reject, |
| 211 // which is usually simply noise. Do not trigger that debug event. |
| 212 %PromiseRejectEvent(promise, r, false); |
| 213 } else { |
| 214 promise = new this(function(resolve, reject) { reject(r) }); |
| 215 } |
| 216 return promise; |
| 217 } |
| 218 |
| 219 // Simple chaining. |
| 220 |
| 221 function PromiseChain(onResolve, onReject) { // a.k.a. flatMap |
| 222 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; |
| 223 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; |
| 224 var deferred = %_CallFunction(this.constructor, PromiseDeferred); |
| 225 switch (GET_PRIVATE(this, promiseStatus)) { |
| 226 case UNDEFINED: |
| 227 throw MakeTypeError('not_a_promise', [this]); |
| 228 case 0: // Pending |
| 229 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); |
| 230 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); |
| 231 break; |
| 232 case +1: // Resolved |
| 233 PromiseEnqueue(GET_PRIVATE(this, promiseValue), |
| 234 [onResolve, deferred], |
| 235 +1); |
| 236 break; |
| 237 case -1: // Rejected |
| 238 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) { |
| 239 // Promise has already been rejected, but had no handler. |
| 240 // Revoke previously triggered reject event. |
| 241 %PromiseRevokeReject(this); |
105 } | 242 } |
106 } | 243 PromiseEnqueue(GET_PRIVATE(this, promiseValue), |
107 return x; | 244 [onReject, deferred], |
108 } | 245 -1); |
109 | 246 break; |
110 function PromiseHandle(value, handler, deferred) { | 247 } |
111 try { | 248 // Mark this promise as having handler. |
112 %DebugPushPromise(deferred.promise, PromiseHandle); | 249 SET_PRIVATE(this, promiseHasHandler, true); |
113 DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler); | 250 if (DEBUG_IS_ACTIVE) { |
114 var result = handler(value); | 251 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); |
115 if (result === deferred.promise) | 252 } |
116 throw MakeTypeError('promise_cyclic', [result]); | 253 return deferred.promise; |
117 else if (IsPromise(result)) | 254 } |
118 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain); | 255 |
119 else | 256 function PromiseCatch(onReject) { |
120 deferred.resolve(result); | 257 return this.then(UNDEFINED, onReject); |
121 } catch (exception) { | 258 } |
122 try { deferred.reject(exception); } catch (e) { } | 259 |
123 } finally { | 260 // Multi-unwrapped chaining with thenable coercion. |
124 %DebugPopPromise(); | 261 |
125 } | 262 function PromiseThen(onResolve, onReject) { |
126 } | 263 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve |
127 | 264 : PromiseIdResolveHandler; |
128 function PromiseEnqueue(value, tasks, status) { | 265 onReject = IS_SPEC_FUNCTION(onReject) ? onReject |
129 var id, name, instrumenting = DEBUG_IS_ACTIVE; | 266 : PromiseIdRejectHandler; |
130 %EnqueueMicrotask(function() { | 267 var that = this; |
131 if (instrumenting) { | 268 var constructor = this.constructor; |
132 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); | 269 return %_CallFunction( |
| 270 this, |
| 271 function(x) { |
| 272 x = PromiseCoerce(constructor, x); |
| 273 if (x === that) { |
| 274 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); |
| 275 return onReject(MakeTypeError('promise_cyclic', [x])); |
| 276 } else if (IsPromise(x)) { |
| 277 return x.then(onResolve, onReject); |
| 278 } else { |
| 279 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); |
| 280 return onResolve(x); |
133 } | 281 } |
134 for (var i = 0; i < tasks.length; i += 2) { | 282 }, |
135 PromiseHandle(value, tasks[i], tasks[i + 1]) | 283 onReject, |
136 } | 284 PromiseChain |
137 if (instrumenting) { | 285 ); |
138 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name }); | 286 } |
139 } | 287 |
140 }); | 288 // Combinators. |
141 if (instrumenting) { | 289 |
142 id = ++lastMicrotaskId; | 290 function PromiseCast(x) { |
143 name = status > 0 ? "Promise.resolve" : "Promise.reject"; | 291 // TODO(rossberg): cannot do better until we support @@create. |
144 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); | 292 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); |
145 } | 293 } |
146 } | 294 |
147 | 295 function PromiseAll(iterable) { |
148 function PromiseIdResolveHandler(x) { return x } | 296 var deferred = %_CallFunction(this, PromiseDeferred); |
149 function PromiseIdRejectHandler(r) { throw r } | 297 var resolutions = []; |
150 | 298 try { |
151 function PromiseNopResolver() {} | 299 var count = 0; |
152 | 300 var i = 0; |
153 // ------------------------------------------------------------------- | 301 for (var value of iterable) { |
154 // Define exported functions. | 302 this.resolve(value).then( |
155 | 303 // Nested scope to get closure over current i. |
156 // For bootstrapper. | 304 // TODO(arv): Use an inner let binding once available. |
157 | 305 (function(i) { |
158 IsPromise = function IsPromise(x) { | 306 return function(x) { |
159 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus); | 307 resolutions[i] = x; |
160 } | 308 if (--count === 0) deferred.resolve(resolutions); |
161 | 309 } |
162 PromiseCreate = function PromiseCreate() { | 310 })(i), |
163 return new $Promise(PromiseNopResolver) | 311 function(r) { deferred.reject(r); }); |
164 } | 312 ++i; |
165 | 313 ++count; |
166 PromiseResolve = function PromiseResolve(promise, x) { | 314 } |
167 PromiseDone(promise, +1, x, promiseOnResolve) | 315 |
168 } | 316 if (count === 0) { |
169 | 317 deferred.resolve(resolutions); |
170 PromiseReject = function PromiseReject(promise, r) { | 318 } |
171 // Check promise status to confirm that this reject has an effect. | 319 |
172 // Call runtime for callbacks to the debugger or for unhandled reject. | 320 } catch (e) { |
173 if (GET_PRIVATE(promise, promiseStatus) == 0) { | 321 deferred.reject(e) |
174 var debug_is_active = DEBUG_IS_ACTIVE; | 322 } |
175 if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) { | 323 return deferred.promise; |
176 %PromiseRejectEvent(promise, r, debug_is_active); | 324 } |
177 } | 325 |
178 } | 326 function PromiseRace(iterable) { |
179 PromiseDone(promise, -1, r, promiseOnReject) | 327 var deferred = %_CallFunction(this, PromiseDeferred); |
180 } | 328 try { |
181 | 329 for (var value of iterable) { |
182 // Convenience. | 330 this.resolve(value).then( |
183 | 331 function(x) { deferred.resolve(x) }, |
184 function PromiseDeferred() { | 332 function(r) { deferred.reject(r) }); |
185 if (this === $Promise) { | 333 } |
186 // Optimized case, avoid extra closure. | 334 } catch (e) { |
187 var promise = PromiseInit(new $Promise(promiseRaw)); | 335 deferred.reject(e) |
188 return { | 336 } |
189 promise: promise, | 337 return deferred.promise; |
190 resolve: function(x) { PromiseResolve(promise, x) }, | 338 } |
191 reject: function(r) { PromiseReject(promise, r) } | 339 |
192 }; | 340 |
193 } else { | 341 // Utility for debugger |
194 var result = {}; | 342 |
195 result.promise = new this(function(resolve, reject) { | 343 function PromiseHasUserDefinedRejectHandlerRecursive(promise) { |
196 result.resolve = resolve; | 344 var queue = GET_PRIVATE(promise, promiseOnReject); |
197 result.reject = reject; | 345 if (IS_UNDEFINED(queue)) return false; |
198 }) | 346 for (var i = 0; i < queue.length; i += 2) { |
199 return result; | 347 if (queue[i] != PromiseIdRejectHandler) return true; |
200 } | 348 if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) { |
201 } | 349 return true; |
202 | 350 } |
203 function PromiseResolved(x) { | 351 } |
204 if (this === $Promise) { | 352 return false; |
205 // Optimized case, avoid extra closure. | 353 } |
206 return PromiseCreateAndSet(+1, x); | 354 |
207 } else { | 355 // Return whether the promise will be handled by a user-defined reject |
208 return new this(function(resolve, reject) { resolve(x) }); | 356 // handler somewhere down the promise chain. For this, we do a depth-first |
209 } | 357 // search for a reject handler that's not the default PromiseIdRejectHandler. |
210 } | 358 function PromiseHasUserDefinedRejectHandler() { |
211 | 359 return PromiseHasUserDefinedRejectHandlerRecursive(this); |
212 function PromiseRejected(r) { | 360 }; |
213 var promise; | 361 |
214 if (this === $Promise) { | 362 // ------------------------------------------------------------------- |
215 // Optimized case, avoid extra closure. | 363 // Install exported functions. |
216 promise = PromiseCreateAndSet(-1, r); | 364 |
217 // The debug event for this would always be an uncaught promise reject, | 365 %AddNamedProperty(global, 'Promise', GlobalPromise, DONT_ENUM); |
218 // which is usually simply noise. Do not trigger that debug event. | 366 %AddNamedProperty(GlobalPromise.prototype, symbolToStringTag, "Promise", |
219 %PromiseRejectEvent(promise, r, false); | 367 DONT_ENUM | READ_ONLY); |
220 } else { | 368 |
221 promise = new this(function(resolve, reject) { reject(r) }); | 369 InstallFunctions(GlobalPromise, DONT_ENUM, [ |
222 } | 370 "defer", PromiseDeferred, |
223 return promise; | 371 "accept", PromiseResolved, |
224 } | 372 "reject", PromiseRejected, |
225 | 373 "all", PromiseAll, |
226 // Simple chaining. | 374 "race", PromiseRace, |
227 | 375 "resolve", PromiseCast |
228 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a. | 376 ]); |
229 // flatMap | 377 |
230 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; | 378 InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ |
231 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; | 379 "chain", PromiseChain, |
232 var deferred = %_CallFunction(this.constructor, PromiseDeferred); | 380 "then", PromiseThen, |
233 switch (GET_PRIVATE(this, promiseStatus)) { | 381 "catch", PromiseCatch |
234 case UNDEFINED: | 382 ]); |
235 throw MakeTypeError('not_a_promise', [this]); | 383 |
236 case 0: // Pending | 384 $promiseCreate = PromiseCreate; |
237 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); | 385 $promiseResolve = PromiseResolve; |
238 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); | 386 $promiseReject = PromiseReject; |
239 break; | 387 $promiseChain = PromiseChain; |
240 case +1: // Resolved | 388 $promiseCatch = PromiseCatch; |
241 PromiseEnqueue(GET_PRIVATE(this, promiseValue), | 389 $promiseThen = PromiseThen; |
242 [onResolve, deferred], | 390 $promiseHasUserDefinedRejectHandler = PromiseHasUserDefinedRejectHandler; |
243 +1); | 391 $promiseStatus = promiseStatus; |
244 break; | 392 $promiseValue = promiseValue; |
245 case -1: // Rejected | |
246 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) { | |
247 // Promise has already been rejected, but had no handler. | |
248 // Revoke previously triggered reject event. | |
249 %PromiseRevokeReject(this); | |
250 } | |
251 PromiseEnqueue(GET_PRIVATE(this, promiseValue), | |
252 [onReject, deferred], | |
253 -1); | |
254 break; | |
255 } | |
256 // Mark this promise as having handler. | |
257 SET_PRIVATE(this, promiseHasHandler, true); | |
258 if (DEBUG_IS_ACTIVE) { | |
259 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); | |
260 } | |
261 return deferred.promise; | |
262 } | |
263 | |
264 PromiseCatch = function PromiseCatch(onReject) { | |
265 return this.then(UNDEFINED, onReject); | |
266 } | |
267 | |
268 // Multi-unwrapped chaining with thenable coercion. | |
269 | |
270 PromiseThen = function PromiseThen(onResolve, onReject) { | |
271 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve | |
272 : PromiseIdResolveHandler; | |
273 onReject = IS_SPEC_FUNCTION(onReject) ? onReject | |
274 : PromiseIdRejectHandler; | |
275 var that = this; | |
276 var constructor = this.constructor; | |
277 return %_CallFunction( | |
278 this, | |
279 function(x) { | |
280 x = PromiseCoerce(constructor, x); | |
281 if (x === that) { | |
282 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject); | |
283 return onReject(MakeTypeError('promise_cyclic', [x])); | |
284 } else if (IsPromise(x)) { | |
285 return x.then(onResolve, onReject); | |
286 } else { | |
287 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve); | |
288 return onResolve(x); | |
289 } | |
290 }, | |
291 onReject, | |
292 PromiseChain | |
293 ); | |
294 } | |
295 | |
296 // Combinators. | |
297 | |
298 function PromiseCast(x) { | |
299 // TODO(rossberg): cannot do better until we support @@create. | |
300 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) }); | |
301 } | |
302 | |
303 function PromiseAll(iterable) { | |
304 var deferred = %_CallFunction(this, PromiseDeferred); | |
305 var resolutions = []; | |
306 try { | |
307 var count = 0; | |
308 var i = 0; | |
309 for (var value of iterable) { | |
310 this.resolve(value).then( | |
311 // Nested scope to get closure over current i. | |
312 // TODO(arv): Use an inner let binding once available. | |
313 (function(i) { | |
314 return function(x) { | |
315 resolutions[i] = x; | |
316 if (--count === 0) deferred.resolve(resolutions); | |
317 } | |
318 })(i), | |
319 function(r) { deferred.reject(r); }); | |
320 ++i; | |
321 ++count; | |
322 } | |
323 | |
324 if (count === 0) { | |
325 deferred.resolve(resolutions); | |
326 } | |
327 | |
328 } catch (e) { | |
329 deferred.reject(e) | |
330 } | |
331 return deferred.promise; | |
332 } | |
333 | |
334 function PromiseRace(iterable) { | |
335 var deferred = %_CallFunction(this, PromiseDeferred); | |
336 try { | |
337 for (var value of iterable) { | |
338 this.resolve(value).then( | |
339 function(x) { deferred.resolve(x) }, | |
340 function(r) { deferred.reject(r) }); | |
341 } | |
342 } catch (e) { | |
343 deferred.reject(e) | |
344 } | |
345 return deferred.promise; | |
346 } | |
347 | |
348 | |
349 // Utility for debugger | |
350 | |
351 function PromiseHasUserDefinedRejectHandlerRecursive(promise) { | |
352 var queue = GET_PRIVATE(promise, promiseOnReject); | |
353 if (IS_UNDEFINED(queue)) return false; | |
354 for (var i = 0; i < queue.length; i += 2) { | |
355 if (queue[i] != PromiseIdRejectHandler) return true; | |
356 if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) { | |
357 return true; | |
358 } | |
359 } | |
360 return false; | |
361 } | |
362 | |
363 // Return whether the promise will be handled by a user-defined reject | |
364 // handler somewhere down the promise chain. For this, we do a depth-first | |
365 // search for a reject handler that's not the default PromiseIdRejectHandler. | |
366 PromiseHasUserDefinedRejectHandler = | |
367 function PromiseHasUserDefinedRejectHandler() { | |
368 return PromiseHasUserDefinedRejectHandlerRecursive(this); | |
369 }; | |
370 | |
371 // ------------------------------------------------------------------- | |
372 // Install exported functions. | |
373 | |
374 %CheckIsBootstrapping(); | |
375 %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM); | |
376 %AddNamedProperty( | |
377 $Promise.prototype, symbolToStringTag, "Promise", DONT_ENUM | READ_ONLY); | |
378 InstallFunctions($Promise, DONT_ENUM, [ | |
379 "defer", PromiseDeferred, | |
380 "accept", PromiseResolved, | |
381 "reject", PromiseRejected, | |
382 "all", PromiseAll, | |
383 "race", PromiseRace, | |
384 "resolve", PromiseCast | |
385 ]); | |
386 InstallFunctions($Promise.prototype, DONT_ENUM, [ | |
387 "chain", PromiseChain, | |
388 "then", PromiseThen, | |
389 "catch", PromiseCatch | |
390 ]); | |
391 | 393 |
392 })(); | 394 })(); |
OLD | NEW |