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

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

Issue 1538663002: Reland "Clean up promises and fix an edge case bug (patchset #4 id:60001 of https://codereview.chro… (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Fixes two test262 tests! Created 4 years, 11 months 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_RECEIVER(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_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol); 159 return IS_RECEIVER(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_RECEIVER(x)) { 168 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x));
198 // 25.4.1.3.2 steps 8-12 169 }
199 try { 170 if (IS_RECEIVER(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;
174 } catch (e) {
175 return PromiseReject(promise, e);
176 }
177 if (IS_CALLABLE(then)) {
178 // PromiseResolveThenableJob
179 var id, name, instrumenting = DEBUG_IS_ACTIVE;
180 %EnqueueMicrotask(function() {
181 if (instrumenting) {
182 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
183 }
184 try {
185 var callbacks = CreateResolvingFunctions(promise);
186 %_Call(then, x, callbacks.resolve, callbacks.reject);
187 } catch (e) {
188 PromiseReject(promise, e);
189 }
190 if (instrumenting) {
191 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
192 }
193 });
194 if (instrumenting) {
195 id = ++lastMicrotaskId;
196 name = "PromseResolveThenableJob";
197 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
203 } 198 }
204 if (IS_CALLABLE(then)) { 199 return;
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 } 200 }
216 PromiseDone(promise, +1, x, promiseOnResolveSymbol);
217 } 201 }
202 PromiseDone(promise, +1, x, promiseOnResolveSymbol);
218 } 203 }
219 204
220 function PromiseReject(promise, r) { 205 function PromiseReject(promise, r) {
221 // Check promise status to confirm that this reject has an effect. 206 // Check promise status to confirm that this reject has an effect.
222 // Call runtime for callbacks to the debugger or for unhandled reject. 207 // Call runtime for callbacks to the debugger or for unhandled reject.
223 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) { 208 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) {
224 var debug_is_active = DEBUG_IS_ACTIVE; 209 var debug_is_active = DEBUG_IS_ACTIVE;
225 if (debug_is_active || 210 if (debug_is_active ||
226 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { 211 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) {
227 %PromiseRejectEvent(promise, r, debug_is_active); 212 %PromiseRejectEvent(promise, r, debug_is_active);
(...skipping 10 matching lines...) Expand all
238 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); 223 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol));
239 var callbacks = CreateResolvingFunctions(promise); 224 var callbacks = CreateResolvingFunctions(promise);
240 return { 225 return {
241 promise: promise, 226 promise: promise,
242 resolve: callbacks.resolve, 227 resolve: callbacks.resolve,
243 reject: callbacks.reject 228 reject: callbacks.reject
244 }; 229 };
245 } else { 230 } else {
246 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; 231 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED };
247 result.promise = new C(function(resolve, reject) { 232 result.promise = new C(function(resolve, reject) {
233 // TODO(littledan): Check for resolve and reject being not undefined
248 result.resolve = resolve; 234 result.resolve = resolve;
249 result.reject = reject; 235 result.reject = reject;
250 }); 236 });
251 return result; 237 return result;
252 } 238 }
253 } 239 }
254 240
255 function PromiseDeferred() { 241 function PromiseDeferred() {
256 return NewPromiseCapability(this); 242 return NewPromiseCapability(this);
257 } 243 }
258 244
259 function PromiseResolved(x) { 245 function PromiseResolved(x) {
260 if (this === GlobalPromise) { 246 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 } 247 }
267 248
268 function PromiseRejected(r) { 249 function PromiseRejected(r) {
250 if (!IS_RECEIVER(this)) {
251 throw MakeTypeError(kCalledOnNonObject, PromiseRejected);
252 }
269 var promise; 253 var promise;
270 if (this === GlobalPromise) { 254 if (this === GlobalPromise) {
271 // Optimized case, avoid extra closure. 255 // Optimized case, avoid extra closure.
272 promise = PromiseCreateAndSet(-1, r); 256 promise = PromiseCreateAndSet(-1, r);
273 // The debug event for this would always be an uncaught promise reject, 257 // The debug event for this would always be an uncaught promise reject,
274 // which is usually simply noise. Do not trigger that debug event. 258 // which is usually simply noise. Do not trigger that debug event.
275 %PromiseRejectEvent(promise, r, false); 259 %PromiseRejectEvent(promise, r, false);
276 } else { 260 } else {
277 promise = new this(function(resolve, reject) { reject(r) }); 261 promise = new this(function(resolve, reject) { reject(r) });
278 } 262 }
279 return promise; 263 return promise;
280 } 264 }
281 265
282 // Simple chaining. 266 // Multi-unwrapped chaining with thenable coercion.
283 267
284 // PromiseChain a.k.a. flatMap 268 function PromiseThen(onResolve, onReject) {
285 function PromiseChainInternal(constructor, onResolve, onReject) { 269 var constructor = this.constructor;
286 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve; 270 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
287 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject; 271 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
288 var deferred = NewPromiseCapability(constructor); 272 var deferred = NewPromiseCapability(constructor);
289 switch (GET_PRIVATE(this, promiseStatusSymbol)) { 273 switch (GET_PRIVATE(this, promiseStatusSymbol)) {
290 case UNDEFINED: 274 case UNDEFINED:
275 // TODO(littledan): The type check should be called before
276 // constructing NewPromiseCapability; this is observable when
277 // erroneously copying this method to other classes.
291 throw MakeTypeError(kNotAPromise, this); 278 throw MakeTypeError(kNotAPromise, this);
292 case 0: // Pending 279 case 0: // Pending
293 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); 280 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred);
294 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred); 281 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred);
295 break; 282 break;
296 case +1: // Resolved 283 case +1: // Resolved
297 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 284 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
298 [onResolve, deferred], 285 [onResolve, deferred],
299 +1); 286 +1);
300 break; 287 break;
301 case -1: // Rejected 288 case -1: // Rejected
302 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) { 289 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) {
303 // Promise has already been rejected, but had no handler. 290 // Promise has already been rejected, but had no handler.
304 // Revoke previously triggered reject event. 291 // Revoke previously triggered reject event.
305 %PromiseRevokeReject(this); 292 %PromiseRevokeReject(this);
306 } 293 }
307 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol), 294 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
308 [onReject, deferred], 295 [onReject, deferred],
309 -1); 296 -1);
310 break; 297 break;
311 } 298 }
312 // Mark this promise as having handler. 299 // Mark this promise as having handler.
313 SET_PRIVATE(this, promiseHasHandlerSymbol, true); 300 SET_PRIVATE(this, promiseHasHandlerSymbol, true);
314 if (DEBUG_IS_ACTIVE) { 301 if (DEBUG_IS_ACTIVE) {
315 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this }); 302 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
316 } 303 }
317 return deferred.promise; 304 return deferred.promise;
318 } 305 }
319 306
307 // Chain is left around for now as an alias for then
320 function PromiseChain(onResolve, onReject) { 308 function PromiseChain(onResolve, onReject) {
321 return %_Call(PromiseChainInternal, this, this.constructor, 309 return %_Call(PromiseThen, this, onResolve, onReject);
322 onResolve, onReject);
323 } 310 }
324 311
325 function PromiseCatch(onReject) { 312 function PromiseCatch(onReject) {
326 return this.then(UNDEFINED, onReject); 313 return this.then(UNDEFINED, onReject);
327 } 314 }
328 315
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. 316 // Combinators.
355 317
356 function PromiseCast(x) { 318 function PromiseCast(x) {
319 if (!IS_RECEIVER(this)) {
320 throw MakeTypeError(kCalledOnNonObject, PromiseCast);
321 }
357 if (IsPromise(x) && x.constructor === this) { 322 if (IsPromise(x) && x.constructor === this) {
358 return x; 323 return x;
359 } else { 324 } else {
360 return new this(function(resolve) { resolve(x) }); 325 return new this(function(resolve, reject) { resolve(x) });
361 } 326 }
362 } 327 }
363 328
364 function PromiseAll(iterable) { 329 function PromiseAll(iterable) {
365 var deferred = NewPromiseCapability(this); 330 var deferred = NewPromiseCapability(this);
366 var resolutions = []; 331 var resolutions = [];
367 try { 332 try {
368 var count = 0; 333 var count = 0;
369 var i = 0; 334 var i = 0;
370 for (var value of iterable) { 335 for (var value of iterable) {
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
478 [PromiseChain, PromiseDeferred, PromiseResolved].forEach( 443 [PromiseChain, PromiseDeferred, PromiseResolved].forEach(
479 fn => %FunctionRemovePrototype(fn)); 444 fn => %FunctionRemovePrototype(fn));
480 445
481 utils.Export(function(to) { 446 utils.Export(function(to) {
482 to.PromiseChain = PromiseChain; 447 to.PromiseChain = PromiseChain;
483 to.PromiseDeferred = PromiseDeferred; 448 to.PromiseDeferred = PromiseDeferred;
484 to.PromiseResolved = PromiseResolved; 449 to.PromiseResolved = PromiseResolved;
485 }); 450 });
486 451
487 }) 452 })
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