blob: bcf826a10151d48a498d40e72f418805a5022f18 [file] [log] [blame]
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001// 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
3// found in the LICENSE file.
4
5(function(global, utils, extrasUtils) {
6
7"use strict";
8
9%CheckIsBootstrapping();
10
11// -------------------------------------------------------------------
12// Imports
13
14var InternalArray = utils.InternalArray;
15var MakeTypeError;
16var promiseCombinedDeferredSymbol =
17 utils.ImportNow("promise_combined_deferred_symbol");
18var promiseHasHandlerSymbol =
19 utils.ImportNow("promise_has_handler_symbol");
20var promiseOnRejectSymbol = utils.ImportNow("promise_on_reject_symbol");
21var promiseOnResolveSymbol =
22 utils.ImportNow("promise_on_resolve_symbol");
23var promiseRawSymbol = utils.ImportNow("promise_raw_symbol");
24var promiseStatusSymbol = utils.ImportNow("promise_status_symbol");
25var promiseValueSymbol = utils.ImportNow("promise_value_symbol");
26var SpeciesConstructor;
27var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
28
29utils.Import(function(from) {
30 MakeTypeError = from.MakeTypeError;
31 SpeciesConstructor = from.SpeciesConstructor;
32});
33
34// -------------------------------------------------------------------
35
36// Status values: 0 = pending, +1 = resolved, -1 = rejected
37var lastMicrotaskId = 0;
38
39function CreateResolvingFunctions(promise) {
40 var alreadyResolved = false;
41
42 var resolve = value => {
43 if (alreadyResolved === true) return;
44 alreadyResolved = true;
45 PromiseResolve(promise, value);
46 };
47
48 var reject = reason => {
49 if (alreadyResolved === true) return;
50 alreadyResolved = true;
51 PromiseReject(promise, reason);
52 };
53
54 return {
55 __proto__: null,
56 resolve: resolve,
57 reject: reject
58 };
59}
60
61
62var GlobalPromise = function Promise(resolver) {
63 if (resolver === promiseRawSymbol) {
Ben Murdochda12d292016-06-02 14:46:10 +010064 return %_NewObject(GlobalPromise, new.target);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000065 }
66 if (IS_UNDEFINED(new.target)) throw MakeTypeError(kNotAPromise, this);
67 if (!IS_CALLABLE(resolver))
68 throw MakeTypeError(kResolverNotAFunction, resolver);
69
Ben Murdochda12d292016-06-02 14:46:10 +010070 var promise = PromiseInit(%_NewObject(GlobalPromise, new.target));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000071 var callbacks = CreateResolvingFunctions(promise);
72
73 try {
74 %DebugPushPromise(promise, Promise);
75 resolver(callbacks.resolve, callbacks.reject);
76 } catch (e) {
77 %_Call(callbacks.reject, UNDEFINED, e);
78 } finally {
79 %DebugPopPromise();
80 }
81
82 return promise;
83}
84
85// Core functionality.
86
87function PromiseSet(promise, status, value, onResolve, onReject) {
88 SET_PRIVATE(promise, promiseStatusSymbol, status);
89 SET_PRIVATE(promise, promiseValueSymbol, value);
90 SET_PRIVATE(promise, promiseOnResolveSymbol, onResolve);
91 SET_PRIVATE(promise, promiseOnRejectSymbol, onReject);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000092 return promise;
93}
94
95function PromiseCreateAndSet(status, value) {
96 var promise = new GlobalPromise(promiseRawSymbol);
97 // If debug is active, notify about the newly created promise first.
98 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED);
99 return PromiseSet(promise, status, value);
100}
101
102function PromiseInit(promise) {
103 return PromiseSet(
104 promise, 0, UNDEFINED, new InternalArray, new InternalArray)
105}
106
107function PromiseDone(promise, status, value, promiseQueue) {
108 if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) {
109 var tasks = GET_PRIVATE(promise, promiseQueue);
110 if (tasks.length) PromiseEnqueue(value, tasks, status);
111 PromiseSet(promise, status, value);
112 }
113}
114
115function PromiseHandle(value, handler, deferred) {
116 try {
117 %DebugPushPromise(deferred.promise, PromiseHandle);
118 var result = handler(value);
119 deferred.resolve(result);
120 } catch (exception) {
121 try { deferred.reject(exception); } catch (e) { }
122 } finally {
123 %DebugPopPromise();
124 }
125}
126
127function PromiseEnqueue(value, tasks, status) {
128 var id, name, instrumenting = DEBUG_IS_ACTIVE;
129 %EnqueueMicrotask(function() {
130 if (instrumenting) {
131 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
132 }
133 for (var i = 0; i < tasks.length; i += 2) {
134 PromiseHandle(value, tasks[i], tasks[i + 1])
135 }
136 if (instrumenting) {
137 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
138 }
139 });
140 if (instrumenting) {
141 id = ++lastMicrotaskId;
142 name = status > 0 ? "Promise.resolve" : "Promise.reject";
143 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
144 }
145}
146
147function PromiseIdResolveHandler(x) { return x }
148function PromiseIdRejectHandler(r) { throw r }
149
150function PromiseNopResolver() {}
151
152// -------------------------------------------------------------------
153// Define exported functions.
154
155// For bootstrapper.
156
157function IsPromise(x) {
158 return IS_RECEIVER(x) && HAS_DEFINED_PRIVATE(x, promiseStatusSymbol);
159}
160
161function PromiseCreate() {
162 return new GlobalPromise(PromiseNopResolver)
163}
164
165function PromiseResolve(promise, x) {
166 if (x === promise) {
167 return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x));
168 }
169 if (IS_RECEIVER(x)) {
170 // 25.4.1.3.2 steps 8-12
171 try {
172 var then = x.then;
173 } catch (e) {
174 return PromiseReject(promise, e);
175 }
176 if (IS_CALLABLE(then)) {
177 // PromiseResolveThenableJob
178 var id, name, instrumenting = DEBUG_IS_ACTIVE;
179 %EnqueueMicrotask(function() {
180 if (instrumenting) {
181 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
182 }
183 var callbacks = CreateResolvingFunctions(promise);
184 try {
185 %_Call(then, x, callbacks.resolve, callbacks.reject);
186 } catch (e) {
187 %_Call(callbacks.reject, UNDEFINED, e);
188 }
189 if (instrumenting) {
190 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
191 }
192 });
193 if (instrumenting) {
194 id = ++lastMicrotaskId;
195 name = "PromseResolveThenableJob";
196 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
197 }
198 return;
199 }
200 }
201 PromiseDone(promise, +1, x, promiseOnResolveSymbol);
202}
203
204function PromiseReject(promise, r) {
205 // Check promise status to confirm that this reject has an effect.
206 // Call runtime for callbacks to the debugger or for unhandled reject.
207 if (GET_PRIVATE(promise, promiseStatusSymbol) == 0) {
208 var debug_is_active = DEBUG_IS_ACTIVE;
209 if (debug_is_active ||
210 !HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) {
211 %PromiseRejectEvent(promise, r, debug_is_active);
212 }
213 }
214 PromiseDone(promise, -1, r, promiseOnRejectSymbol)
215}
216
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000217function NewPromiseCapability(C) {
218 if (C === GlobalPromise) {
219 // Optimized case, avoid extra closure.
220 var promise = PromiseInit(new GlobalPromise(promiseRawSymbol));
221 var callbacks = CreateResolvingFunctions(promise);
222 return {
223 promise: promise,
224 resolve: callbacks.resolve,
225 reject: callbacks.reject
226 };
227 }
228
229 var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED };
230 result.promise = new C((resolve, reject) => {
231 if (!IS_UNDEFINED(result.resolve) || !IS_UNDEFINED(result.reject))
232 throw MakeTypeError(kPromiseExecutorAlreadyInvoked);
233 result.resolve = resolve;
234 result.reject = reject;
235 });
236
Ben Murdochda12d292016-06-02 14:46:10 +0100237 if (!IS_CALLABLE(result.resolve) || !IS_CALLABLE(result.reject))
238 throw MakeTypeError(kPromiseNonCallable);
239
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000240 return result;
241}
242
243function PromiseDeferred() {
244 %IncrementUseCounter(kPromiseDefer);
245 return NewPromiseCapability(this);
246}
247
248function PromiseResolved(x) {
249 %IncrementUseCounter(kPromiseAccept);
250 return %_Call(PromiseCast, this, x);
251}
252
253function PromiseRejected(r) {
254 if (!IS_RECEIVER(this)) {
255 throw MakeTypeError(kCalledOnNonObject, PromiseRejected);
256 }
257 if (this === GlobalPromise) {
258 // Optimized case, avoid extra closure.
259 var promise = PromiseCreateAndSet(-1, r);
260 // The debug event for this would always be an uncaught promise reject,
261 // which is usually simply noise. Do not trigger that debug event.
262 %PromiseRejectEvent(promise, r, false);
263 return promise;
264 } else {
265 var promiseCapability = NewPromiseCapability(this);
266 %_Call(promiseCapability.reject, UNDEFINED, r);
267 return promiseCapability.promise;
268 }
269}
270
271// Multi-unwrapped chaining with thenable coercion.
272
273function PromiseThen(onResolve, onReject) {
274 var status = GET_PRIVATE(this, promiseStatusSymbol);
275 if (IS_UNDEFINED(status)) {
276 throw MakeTypeError(kNotAPromise, this);
277 }
278
279 var constructor = SpeciesConstructor(this, GlobalPromise);
280 onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
281 onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
282 var deferred = NewPromiseCapability(constructor);
283 switch (status) {
284 case 0: // Pending
285 GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred);
286 GET_PRIVATE(this, promiseOnRejectSymbol).push(onReject, deferred);
287 break;
288 case +1: // Resolved
289 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
290 [onResolve, deferred],
291 +1);
292 break;
293 case -1: // Rejected
294 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) {
295 // Promise has already been rejected, but had no handler.
296 // Revoke previously triggered reject event.
297 %PromiseRevokeReject(this);
298 }
299 PromiseEnqueue(GET_PRIVATE(this, promiseValueSymbol),
300 [onReject, deferred],
301 -1);
302 break;
303 }
304 // Mark this promise as having handler.
305 SET_PRIVATE(this, promiseHasHandlerSymbol, true);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000306 return deferred.promise;
307}
308
309// Chain is left around for now as an alias for then
310function PromiseChain(onResolve, onReject) {
311 %IncrementUseCounter(kPromiseChain);
312 return %_Call(PromiseThen, this, onResolve, onReject);
313}
314
315function PromiseCatch(onReject) {
316 return this.then(UNDEFINED, onReject);
317}
318
319// Combinators.
320
321function PromiseCast(x) {
322 if (!IS_RECEIVER(this)) {
323 throw MakeTypeError(kCalledOnNonObject, PromiseCast);
324 }
325 if (IsPromise(x) && x.constructor === this) return x;
326
327 var promiseCapability = NewPromiseCapability(this);
328 var resolveResult = %_Call(promiseCapability.resolve, UNDEFINED, x);
329 return promiseCapability.promise;
330}
331
332function PromiseAll(iterable) {
333 if (!IS_RECEIVER(this)) {
334 throw MakeTypeError(kCalledOnNonObject, "Promise.all");
335 }
336
337 var deferred = NewPromiseCapability(this);
338 var resolutions = new InternalArray();
339 var count;
340
341 function CreateResolveElementFunction(index, values, promiseCapability) {
342 var alreadyCalled = false;
343 return (x) => {
344 if (alreadyCalled === true) return;
345 alreadyCalled = true;
346 values[index] = x;
347 if (--count === 0) {
348 var valuesArray = [];
349 %MoveArrayContents(values, valuesArray);
350 %_Call(promiseCapability.resolve, UNDEFINED, valuesArray);
351 }
352 };
353 }
354
355 try {
356 var i = 0;
357 count = 1;
358 for (var value of iterable) {
359 var nextPromise = this.resolve(value);
360 ++count;
361 nextPromise.then(
362 CreateResolveElementFunction(i, resolutions, deferred),
363 deferred.reject);
364 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred);
365 ++i;
366 }
367
368 // 6.d
369 if (--count === 0) {
370 var valuesArray = [];
371 %MoveArrayContents(resolutions, valuesArray);
372 %_Call(deferred.resolve, UNDEFINED, valuesArray);
373 }
374
375 } catch (e) {
376 %_Call(deferred.reject, UNDEFINED, e);
377 }
378 return deferred.promise;
379}
380
381function PromiseRace(iterable) {
382 if (!IS_RECEIVER(this)) {
383 throw MakeTypeError(kCalledOnNonObject, PromiseRace);
384 }
385
386 var deferred = NewPromiseCapability(this);
387 try {
388 for (var value of iterable) {
389 this.resolve(value).then(deferred.resolve, deferred.reject);
390 SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred);
391 }
392 } catch (e) {
393 deferred.reject(e)
394 }
395 return deferred.promise;
396}
397
398
399// Utility for debugger
400
401function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
402 var queue = GET_PRIVATE(promise, promiseOnRejectSymbol);
403 if (IS_UNDEFINED(queue)) return false;
404 for (var i = 0; i < queue.length; i += 2) {
405 var handler = queue[i];
406 if (handler !== PromiseIdRejectHandler) {
407 var deferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol);
408 if (IS_UNDEFINED(deferred)) return true;
409 if (PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise)) {
410 return true;
411 }
412 } else if (PromiseHasUserDefinedRejectHandlerRecursive(
413 queue[i + 1].promise)) {
414 return true;
415 }
416 }
417 return false;
418}
419
420// Return whether the promise will be handled by a user-defined reject
421// handler somewhere down the promise chain. For this, we do a depth-first
422// search for a reject handler that's not the default PromiseIdRejectHandler.
423function PromiseHasUserDefinedRejectHandler() {
424 return PromiseHasUserDefinedRejectHandlerRecursive(this);
425};
426
427// -------------------------------------------------------------------
428// Install exported functions.
429
430%AddNamedProperty(global, 'Promise', GlobalPromise, DONT_ENUM);
431%AddNamedProperty(GlobalPromise.prototype, toStringTagSymbol, "Promise",
432 DONT_ENUM | READ_ONLY);
433
434utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
435 "reject", PromiseRejected,
436 "all", PromiseAll,
437 "race", PromiseRace,
438 "resolve", PromiseCast
439]);
440
441utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [
442 "then", PromiseThen,
443 "catch", PromiseCatch
444]);
445
446%InstallToContext([
447 "promise_catch", PromiseCatch,
448 "promise_chain", PromiseChain,
449 "promise_create", PromiseCreate,
450 "promise_has_user_defined_reject_handler", PromiseHasUserDefinedRejectHandler,
451 "promise_reject", PromiseReject,
452 "promise_resolve", PromiseResolve,
453 "promise_then", PromiseThen,
454]);
455
456// This allows extras to create promises quickly without building extra
457// resolve/reject closures, and allows them to later resolve and reject any
458// promise without having to hold on to those closures forever.
459utils.InstallFunctions(extrasUtils, 0, [
460 "createPromise", PromiseCreate,
461 "resolvePromise", PromiseResolve,
462 "rejectPromise", PromiseReject
463]);
464
465// TODO(v8:4567): Allow experimental natives to remove function prototype
466[PromiseChain, PromiseDeferred, PromiseResolved].forEach(
467 fn => %FunctionRemovePrototype(fn));
468
469utils.Export(function(to) {
470 to.PromiseChain = PromiseChain;
471 to.PromiseDeferred = PromiseDeferred;
472 to.PromiseResolved = PromiseResolved;
473});
474
475})