Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: src/js/promise.js

Issue 1488783002: Clean up promises and fix an edge case bug (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Minor code cleanup; disable some soon-to-be-obsolete mjsunit tests Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | test/mjsunit/es6/promises.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 (function(global, utils, extrasUtils) { 5 (function(global, utils, extrasUtils) {
6 6
7 "use strict"; 7 "use strict";
8 8
9 %CheckIsBootstrapping(); 9 %CheckIsBootstrapping();
10 10
(...skipping 22 matching lines...) Expand all
33 33
34 // Status values: 0 = pending, +1 = resolved, -1 = rejected 34 // Status values: 0 = pending, +1 = resolved, -1 = rejected
35 var lastMicrotaskId = 0; 35 var lastMicrotaskId = 0;
36 36
37 function CreateResolvingFunctions(promise) { 37 function CreateResolvingFunctions(promise) {
38 var alreadyResolved = false; 38 var alreadyResolved = false;
39 39
40 var resolve = function(value) { 40 var resolve = function(value) {
41 if (alreadyResolved === true) return; 41 if (alreadyResolved === true) return;
42 alreadyResolved = true; 42 alreadyResolved = true;
43 if (value === promise) {
44 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value));
45 }
46 PromiseResolve(promise, value); 43 PromiseResolve(promise, value);
47 }; 44 };
48 45
49 var reject = function(reason) { 46 var reject = function(reason) {
50 if (alreadyResolved === true) return; 47 if (alreadyResolved === true) return;
51 alreadyResolved = true; 48 alreadyResolved = true;
52 PromiseReject(promise, reason); 49 PromiseReject(promise, reason);
53 }; 50 };
54 51
55 return { 52 return {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 } 106 }
110 107
111 function PromiseDone(promise, status, value, promiseQueue) { 108 function PromiseDone(promise, status, value, promiseQueue) {
112 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { 109 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) {
113 var tasks = GET_PRIVATE(promise, promiseQueue); 110 var tasks = GET_PRIVATE(promise, promiseQueue);
114 if (tasks.length) PromiseEnqueue(value, tasks, status); 111 if (tasks.length) PromiseEnqueue(value, tasks, status);
115 PromiseSet(promise, status, value); 112 PromiseSet(promise, status, value);
116 } 113 }
117 } 114 }
118 115
119 function PromiseCoerce(constructor, x) {
120 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
121 var then;
122 try {
123 then = x.then;
124 } catch(r) {
125 return %_Call(PromiseRejected, constructor, r);
126 }
127 if (IS_CALLABLE(then)) {
128 var deferred = NewPromiseCapability(constructor);
129 try {
130 %_Call(then, x, deferred.resolve, deferred.reject);
131 } catch(r) {
132 deferred.reject(r);
133 }
134 return deferred.promise;
135 }
136 }
137 return x;
138 }
139
140 function PromiseHandle(value, handler, deferred) { 116 function PromiseHandle(value, handler, deferred) {
141 try { 117 try {
142 %DebugPushPromise(deferred.promise, PromiseHandle); 118 %DebugPushPromise(deferred.promise, PromiseHandle);
143 var result = handler(value); 119 var result = handler(value);
144 if (result === deferred.promise) 120 deferred.resolve(result);
145 throw MakeTypeError(kPromiseCyclic, result);
146 else if (IsPromise(result))
147 %_Call(PromiseChain, result, deferred.resolve, deferred.reject);
148 else
149 deferred.resolve(result);
150 } catch (exception) { 121 } catch (exception) {
151 try { deferred.reject(exception); } catch (e) { } 122 try { deferred.reject(exception); } catch (e) { }
152 } finally { 123 } finally {
153 %DebugPopPromise(); 124 %DebugPopPromise();
154 } 125 }
155 } 126 }
156 127
157 function PromiseEnqueue(value, tasks, status) { 128 function PromiseEnqueue(value, tasks, status) {
158 var id, name, instrumenting = DEBUG_IS_ACTIVE; 129 var id, name, instrumenting = DEBUG_IS_ACTIVE;
159 %EnqueueMicrotask(function() { 130 %EnqueueMicrotask(function() {
(...skipping 26 matching lines...) Expand all
186 157
187 function IsPromise(x) { 158 function IsPromise(x) {
188 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); 159 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol);
189 } 160 }
190 161
191 function PromiseCreate() { 162 function PromiseCreate() {
192 return new GlobalPromise(PromiseNopResolver) 163 return new GlobalPromise(PromiseNopResolver)
193 } 164 }
194 165
195 function PromiseResolve(promise, x) { 166 function PromiseResolve(promise, x) {
196 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) { 167 if (x === promise) {
197 if (IS_SPEC_OBJECT(x)) { 168 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x));
198 // 25.4.1.3.2 steps 8-12 169 }
199 try { 170 if (IS_SPEC_OBJECT(x)) {
200 var then = x.then; 171 // 25.4.1.3.2 steps 8-12
201 } catch (e) { 172 try {
202 return PromiseReject(promise, e); 173 var then = x.then;
203 } 174 } catch (e) {
204 if (IS_CALLABLE(then)) { 175 return PromiseReject(promise, e);
205 // PromiseResolveThenableJob
206 return %EnqueueMicrotask(function() {
207 try {
208 var callbacks = CreateResolvingFunctions(promise);
209 %_Call(then, x, callbacks.resolve, callbacks.reject);
210 } catch (e) {
211 PromiseReject(promise, e);
212 }
213 });
214 }
215 } 176 }
216 PromiseDone(promise, +1, x, promiseOnResolveSymbol); 177 if (IS_CALLABLE(then)) {
178 // PromiseResolveThenableJob
179 return %EnqueueMicrotask(function() {
180 try {
181 var callbacks = CreateResolvingFunctions(promise);
182 %_Call(then, x, callbacks.resolve, callbacks.reject);
183 } catch (e) {
184 PromiseReject(promise, e);
185 }
186 });
187 }
217 } 188 }
189 PromiseDone(promise, +1, x, promiseOnResolveSymbol);
218 } 190 }
219 191
220 function PromiseReject(promise, r) { 192 function PromiseReject(promise, r) {
221 // Check promise status to confirm that this reject has an effect. 193 // Check promise status to confirm that this reject has an effect.
222 // Call runtime for callbacks to the debugger or for unhandled reject. 194 // Call runtime for callbacks to the debugger or for unhandled reject.
223 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { 195 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) {
224 var debug_is_active = DEBUG_IS_ACTIVE; 196 var debug_is_active = DEBUG_IS_ACTIVE;
225 if (debug_is_active || 197 if (debug_is_active ||
226 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { 198 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) {
227 %PromiseRejectEvent(promise, r, debug_is_active); 199 %PromiseRejectEvent(promise, r, debug_is_active);
(...skipping 10 matching lines...) Expand all
238 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); 210 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol));
239 var callbacks = CreateResolvingFunctions(promise); 211 var callbacks = CreateResolvingFunctions(promise);
240 return { 212 return {
241 promise: promise, 213 promise: promise,
242 resolve: callbacks.resolve, 214 resolve: callbacks.resolve,
243 reject: callbacks.reject 215 reject: callbacks.reject
244 }; 216 };
245 } else { 217 } else {
246 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; 218 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED };
247 result.promise = new C(function(resolve, reject) { 219 result.promise = new C(function(resolve, reject) {
220 // TODO(littledan): Check for resolve and reject being not undefined
248 result.resolve = resolve; 221 result.resolve = resolve;
249 result.reject = reject; 222 result.reject = reject;
250 }); 223 });
251 return result; 224 return result;
252 } 225 }
253 } 226 }
254 227
255 function PromiseDeferred() { 228 function PromiseDeferred() {
256 return NewPromiseCapability(this); 229 return NewPromiseCapability(this);
257 } 230 }
258 231
259 function PromiseResolved(x) { 232 function PromiseResolved(x) {
260 if (this === GlobalPromise) { 233 return %_Call(PromiseCast, this, x);
261 // Optimized case, avoid extra closure.
262 return PromiseCreateAndSet(+1, x);
263 } else {
264 return new this(function(resolve, reject) { resolve(x) });
265 }
266 } 234 }
267 235
268 function PromiseRejected(r) { 236 function PromiseRejected(r) {
237 if (!IS_SPEC_OBJECT(this)) {
238 throw MakeTypeError(kCalledOnNonObject, PromiseRejected);
239 }
269 var promise; 240 var promise;
270 if (this === GlobalPromise) { 241 if (this === GlobalPromise) {
271 // Optimized case, avoid extra closure. 242 // Optimized case, avoid extra closure.
272 promise = PromiseCreateAndSet(-1, r); 243 promise = PromiseCreateAndSet(-1, r);
273 // The debug event for this would always be an uncaught promise reject, 244 // The debug event for this would always be an uncaught promise reject,
274 // which is usually simply noise. Do not trigger that debug event. 245 // which is usually simply noise. Do not trigger that debug event.
275 %PromiseRejectEvent(promise, r, false); 246 %PromiseRejectEvent(promise, r, false);
276 } else { 247 } else {
277 promise = new this(function(resolve, reject) { reject(r) }); 248 promise = new this(function(resolve, reject) { reject(r) });
278 } 249 }
279 return promise; 250 return promise;
280 } 251 }
281 252
282 // Simple chaining. 253 // Multi-unwrapped chaining with thenable coercion.
283 254
284 // PromiseChain a.k.a. flatMap 255 function PromiseThen(onResolve, onReject) {
285 function PromiseChainInternal(constructor, onResolve, onReject) { 256 var constructor = this.constructor;
286 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; 257 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
287 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; 258 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
288 var deferred = NewPromiseCapability(constructor); 259 var deferred = NewPromiseCapability(constructor);
289 switch (GET_PRIVATE(this, promiseStatusSymbol)) { 260 switch (GET_PRIVATE(this, promiseStatusSymbol)) {
290 case UNDEFINED: 261 case UNDEFINED:
262 // TODO(littledan): The type check should be called before
263 // constructing NewPromiseCapability; this is observable when
264 // erroneously copying this method to other classes.
291 throw MakeTypeError(kNotAPromise, this); 265 throw MakeTypeError(kNotAPromise, this);
292 case 0: // Pending 266 case 0: // Pending
293 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); 267 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred);
294 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); 268 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred);
295 break; 269 break;
296 case +1: // Resolved 270 case +1: // Resolved
297 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 271 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
298 [onResolve, deferred], 272 [onResolve, deferred],
299 +1); 273 +1);
300 break; 274 break;
301 case -1: // Rejected 275 case -1: // Rejected
302 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { 276 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) {
303 // Promise has already been rejected, but had no handler. 277 // Promise has already been rejected, but had no handler.
304 // Revoke previously triggered reject event. 278 // Revoke previously triggered reject event.
305 %PromiseRevokeReject(this); 279 %PromiseRevokeReject(this);
306 } 280 }
307 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 281 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
308 [onReject, deferred], 282 [onReject, deferred],
309 -1); 283 -1);
310 break; 284 break;
311 } 285 }
312 // Mark this promise as having handler. 286 // Mark this promise as having handler.
313 SET_PRIVATE(this, promiseHasHandlerSymbol, true); 287 SET_PRIVATE(this, promiseHasHandlerSymbol, true);
314 if (DEBUG_IS_ACTIVE) { 288 if (DEBUG_IS_ACTIVE) {
315 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); 289 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
316 } 290 }
317 return deferred.promise; 291 return deferred.promise;
318 } 292 }
319 293
294 // Chain is left around for now as an alias for then
320 function PromiseChain(onResolve, onReject) { 295 function PromiseChain(onResolve, onReject) {
321 return %_Call(PromiseChainInternal, this, this.constructor, 296 return %_Call(PromiseThen, this, onResolve, onReject);
322 onResolve, onReject);
323 } 297 }
324 298
325 function PromiseCatch(onReject) { 299 function PromiseCatch(onReject) {
326 return this.then(UNDEFINED, onReject); 300 return this.then(UNDEFINED, onReject);
327 } 301 }
328 302
329 // Multi-unwrapped chaining with thenable coercion.
330
331 function PromiseThen(onResolve, onReject) {
332 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
333 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
334 var that = this;
335 var constructor = this.constructor;
336 return %_Call(
337 PromiseChainInternal,
338 this,
339 constructor,
340 function(x) {
341 x = PromiseCoerce(constructor, x);
342 if (x === that) {
343 return onReject(MakeTypeError(kPromiseCyclic, x));
344 } else if (IsPromise(x)) {
345 return x.then(onResolve, onReject);
346 } else {
347 return onResolve(x);
348 }
349 },
350 onReject
351 );
352 }
353
354 // Combinators. 303 // Combinators.
355 304
356 function PromiseCast(x) { 305 function PromiseCast(x) {
306 if (!IS_SPEC_OBJECT(this)) {
307 throw MakeTypeError(kCalledOnNonObject, PromiseCast);
308 }
357 if (IsPromise(x) && x.constructor === this) { 309 if (IsPromise(x) && x.constructor === this) {
358 return x; 310 return x;
359 } else { 311 } else {
360 return new this(function(resolve) { resolve(x) }); 312 return new this(function(resolve, reject) { resolve(x) });
361 } 313 }
362 } 314 }
363 315
364 function PromiseAll(iterable) { 316 function PromiseAll(iterable) {
365 var deferred = NewPromiseCapability(this); 317 var deferred = NewPromiseCapability(this);
366 var resolutions = []; 318 var resolutions = [];
367 try { 319 try {
368 var count = 0; 320 var count = 0;
369 var i = 0; 321 var i = 0;
370 for (var value of iterable) { 322 for (var value of iterable) {
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
477 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( 429 [PromiseChain, PromiseDeferred, PromiseResolved].forEach(
478 fn => %FunctionRemovePrototype(fn)); 430 fn => %FunctionRemovePrototype(fn));
479 431
480 utils.Export(function(to) { 432 utils.Export(function(to) {
481 to.PromiseChain = PromiseChain; 433 to.PromiseChain = PromiseChain;
482 to.PromiseDeferred = PromiseDeferred; 434 to.PromiseDeferred = PromiseDeferred;
483 to.PromiseResolved = PromiseResolved; 435 to.PromiseResolved = PromiseResolved;
484 }); 436 });
485 437
486 }) 438 })
OLDNEW
« no previous file with comments | « no previous file | test/mjsunit/es6/promises.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698