OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2012 the V8 project authors. All rights reserved. | |
2 // Redistribution and use in source and binary forms, with or without | |
3 // modification, are permitted provided that the following conditions are | |
4 // met: | |
5 // | |
6 // * Redistributions of source code must retain the above copyright | |
7 // notice, this list of conditions and the following disclaimer. | |
8 // * Redistributions in binary form must reproduce the above | |
9 // copyright notice, this list of conditions and the following | |
10 // disclaimer in the documentation and/or other materials provided | |
11 // with the distribution. | |
12 // * Neither the name of Google Inc. nor the names of its | |
13 // contributors may be used to endorse or promote products derived | |
14 // from this software without specific prior written permission. | |
15 // | |
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 | |
28 | |
29 "use strict"; | |
30 | |
31 // This file relies on the fact that the following declaration has been made | |
32 // in runtime.js: | |
33 // var $Object = global.Object | |
34 // var $WeakMap = global.WeakMap | |
35 | |
36 | |
37 var $Promise = Promise; | |
38 | |
39 | |
40 //------------------------------------------------------------------- | |
41 | |
42 // Core functionality. | |
43 | |
44 // Event queue format: [(value, [(handler, deferred)*])*] | |
45 // I.e., a list of value/tasks pairs, where the value is a resolution value or | |
46 // rejection reason, and the tasks are a respective list of handler/deferred | |
47 // pairs waiting for notification of this value. Each handler is an onResolve or | |
48 // onReject function provided to the same call of 'when' that produced the | |
49 // associated deferred. | |
50 var promiseEvents = new InternalArray; | |
51 | |
52 // Status values: 0 = pending, +1 = resolved, -1 = rejected | |
53 var promiseStatus = NEW_PRIVATE("Promise#status"); | |
54 var promiseValue = NEW_PRIVATE("Promise#value"); | |
55 var promiseOnResolve = NEW_PRIVATE("Promise#onResolve"); | |
56 var promiseOnReject = NEW_PRIVATE("Promise#onReject"); | |
57 var promiseRaw = NEW_PRIVATE("Promise#raw"); | |
58 | |
59 function IsPromise(x) { | |
60 return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus); | |
61 } | |
62 | |
63 function Promise(resolver) { | |
64 if (resolver === promiseRaw) return; | |
65 var promise = PromiseInit(this); | |
66 resolver(function(x) { PromiseResolve(promise, x) }, | |
67 function(r) { PromiseReject(promise, r) }); | |
68 } | |
69 | |
70 function PromiseSet(promise, status, value, onResolve, onReject) { | |
71 SET_PRIVATE(promise, promiseStatus, status); | |
72 SET_PRIVATE(promise, promiseValue, value); | |
73 SET_PRIVATE(promise, promiseOnResolve, onResolve); | |
74 SET_PRIVATE(promise, promiseOnReject, onReject); | |
75 return promise; | |
76 } | |
77 | |
78 function PromiseInit(promise) { | |
79 return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray) | |
80 } | |
81 | |
82 function PromiseDone(promise, status, value, promiseQueue) { | |
83 if (GET_PRIVATE(promise, promiseStatus) !== 0) | |
84 throw MakeTypeError('promise_not_pending', [promise]); | |
85 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue)); | |
86 PromiseSet(promise, status, value); | |
87 } | |
88 | |
89 function PromiseResolve(promise, x) { | |
90 PromiseDone(promise, +1, x, promiseOnResolve) | |
91 } | |
92 | |
93 function PromiseReject(promise, r) { | |
94 PromiseDone(promise, -1, r, promiseOnReject) | |
95 } | |
96 | |
97 | |
98 // Convenience. | |
99 | |
100 function PromiseDeferred() { | |
101 if (this === $Promise) { | |
102 // Optimized case, avoid extra closure. | |
103 var promise = PromiseInit(new Promise(promiseRaw)); | |
104 return { | |
105 promise: promise, | |
106 resolve: function(x) { PromiseResolve(promise, x) }, | |
107 reject: function(r) { PromiseReject(promise, r) } | |
108 }; | |
109 } else { | |
110 var result = {}; | |
111 result.promise = new this(function(resolve, reject) { | |
112 result.resolve = resolve; | |
113 result.reject = reject; | |
114 }) | |
115 return result; | |
116 } | |
117 } | |
118 | |
119 function PromiseResolved(x) { | |
120 if (this === $Promise) { | |
121 // Optimized case, avoid extra closure. | |
122 return PromiseSet(new Promise(promiseRaw), +1, x); | |
123 } else { | |
124 return new this(function(resolve, reject) { resolve(x) }); | |
125 } | |
126 } | |
127 | |
128 function PromiseRejected(r) { | |
129 if (this === $Promise) { | |
130 // Optimized case, avoid extra closure. | |
131 return PromiseSet(new Promise(promiseRaw), -1, r); | |
132 } else { | |
133 return new this(function(resolve, reject) { reject(r) }); | |
134 } | |
135 } | |
136 | |
137 | |
138 // Simple chaining (a.k.a. flatMap). | |
139 | |
140 function PromiseNopHandler() {} | |
141 | |
142 function PromiseWhen(onResolve, onReject) { | |
143 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; | |
144 onReject = IS_UNDEFINED(onReject) ? PromiseNopHandler : onReject; | |
145 var deferred = %_CallFunction(this.constructor, PromiseDeferred); | |
146 switch (GET_PRIVATE(this, promiseStatus)) { | |
147 case UNDEFINED: | |
148 throw MakeTypeError('not_a_promise', [this]); | |
149 case 0: // Pending | |
150 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred); | |
151 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred); | |
152 break; | |
153 case +1: // Resolved | |
154 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]); | |
155 break; | |
156 case -1: // Rejected | |
157 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]); | |
158 break; | |
159 } | |
160 return deferred.promise; | |
161 } | |
162 | |
163 function PromiseCatch(onReject) { | |
164 return this.when(UNDEFINED, onReject); | |
165 } | |
166 | |
167 function PromiseEnqueue(value, tasks) { | |
168 promiseEvents.push(value, tasks); | |
169 %SetMicrotasksPending(true); | |
170 } | |
171 | |
172 function PromiseMicrotasksRunner() { | |
173 var events = promiseEvents; | |
174 if (events.length > 0) { | |
175 promiseEvents = new InternalArray; | |
176 for (var i = 0; i < events.length; i += 2) { | |
177 var value = events[i]; | |
178 var tasks = events[i + 1]; | |
179 for (var j = 0; j < tasks.length; j += 2) { | |
180 var handler = tasks[j]; | |
181 var deferred = tasks[j + 1]; | |
182 try { | |
183 var result = handler(value); | |
184 if (result === deferred.promise) | |
185 throw MakeTypeError('promise_cyclic', [result]); | |
186 else if (IsPromise(result)) | |
187 result.when(deferred.resolve, deferred.reject); | |
188 else | |
189 deferred.resolve(result); | |
190 } catch(e) { | |
191 // TODO(rossberg): perhaps log uncaught exceptions below. | |
192 try { deferred.reject(e) } catch(e) {} | |
193 } | |
194 } | |
195 } | |
196 } | |
197 } | |
198 RunMicrotasks.runners.push(PromiseMicrotasksRunner); | |
199 | |
200 | |
201 // Extended functionality for multi-unwrapping chaining and coercive 'then'. | |
202 | |
203 function PromiseThen(onResolve, onReject) { | |
204 onResolve = IS_UNDEFINED(onResolve) ? PromiseNopHandler : onResolve; | |
205 var that = this; | |
206 var constructor = this.constructor; | |
207 return this.when( | |
208 function(x) { | |
209 x = PromiseCoerce(constructor, x); | |
210 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) : | |
211 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x); | |
212 }, | |
213 onReject | |
214 ); | |
215 } | |
216 | |
217 PromiseCoerce.table = new $WeakMap; | |
218 | |
219 function PromiseCoerce(constructor, x) { | |
220 if (IsPromise(x)) { | |
221 return x; | |
222 } else if (!IS_NULL_OR_UNDEFINED(x) && 'then' in TO_OBJECT_INLINE(x)) { | |
223 if (PromiseCoerce.table.has(x)) { | |
224 return PromiseCoerce.table.get(x); | |
225 } else { | |
226 var deferred = constructor.deferred(); | |
227 PromiseCoerce.table.set(x, deferred.promise); | |
228 try { | |
229 x.then(deferred.resolve, deferred.reject); | |
230 } catch(e) { | |
231 deferred.reject(e); | |
232 } | |
233 return deferred.promise; | |
234 } | |
235 } else { | |
236 return x; | |
237 } | |
238 } | |
239 | |
240 | |
241 // Combinators. | |
242 | |
243 function PromiseCast(x) { | |
244 // TODO(rossberg): cannot do better until we support @@create. | |
245 return IsPromise(x) ? x : this.resolved(x); | |
246 } | |
247 | |
248 function PromiseAll(values) { | |
yhirano
2013/11/18 03:59:04
This function returns a promise which will never b
rossberg
2013/11/18 10:56:05
Oops, I somehow managed to misread the spec 8-}, a
yhirano
2013/11/18 11:03:46
Sorry, my comment was wrong.
I wanted to say:
Thi
rossberg
2013/11/18 11:51:09
Ah, excellent point. Fixed and test case added (al
| |
249 var deferred = this.deferred(); | |
250 var count = 0; | |
251 for (var i = 0; i < values.length; ++i) { | |
252 ++count; | |
253 this.cast(values[i]).when( | |
254 function(x) { if (--count === 0) deferred.resolve() }, | |
255 function(r) { if (count > 0) { count = 0; deferred.reject(r) } } | |
256 ); | |
257 } | |
258 return deferred.promise; | |
259 } | |
260 | |
261 function PromiseOne(values) { | |
262 var deferred = this.deferred(); | |
263 var done = false; | |
264 for (var i = 0; i < values.length; ++i) { | |
265 this.cast(values[i]).when( | |
266 function(x) { if (!done) { done = true; deferred.resolve(x) } }, | |
267 function(r) { if (!done) { done = true; deferred.reject(r) } } | |
268 ); | |
269 } | |
270 return deferred.promise; | |
271 } | |
272 | |
273 //------------------------------------------------------------------- | |
274 | |
275 function SetUpPromise() { | |
276 %CheckIsBootstrapping() | |
277 global.Promise = $Promise; | |
278 InstallFunctions($Promise, DONT_ENUM, [ | |
279 "deferred", PromiseDeferred, | |
280 "resolved", PromiseResolved, | |
281 "rejected", PromiseRejected, | |
282 "all", PromiseAll, | |
283 "one", PromiseOne, | |
yhirano
2013/11/18 03:59:04
Is this Promise.race?
rossberg
2013/11/18 10:56:05
Yes, added a comment.
| |
284 "cast", PromiseCast | |
285 ]); | |
286 InstallFunctions($Promise.prototype, DONT_ENUM, [ | |
287 "when", PromiseWhen, | |
yhirano
2013/11/18 03:59:04
There is no Promise.prototype.when in the spec dra
yhirano
2013/11/18 04:01:31
Oh, I didn't read the description. Never mind.
| |
288 "then", PromiseThen, | |
289 "catch", PromiseCatch | |
290 ]); | |
291 } | |
292 | |
293 SetUpPromise(); | |
OLD | NEW |