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
3 // found in the LICENSE file.
7 // This file relies on the fact that the following declaration has been made
9 // var $Object = global.Object
10 // var $WeakMap = global.WeakMap
21 var PromiseHasRejectHandler;
22 var PromiseHasUserDefinedRejectHandler;
24 // mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
25 // if we could move these property names into the closure below.
26 // TODO(jkummerow/rossberg/yangguo): Find a better solution.
28 // Status values: 0 = pending, +1 = resolved, -1 = rejected
29 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
30 var promiseValue = GLOBAL_PRIVATE("Promise#value");
31 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
32 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
33 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
34 var promiseHasHandler = %PromiseHasHandlerSymbol();
35 var lastMicrotaskId = 0;
40 var $Promise = function Promise(resolver) {
41 if (resolver === promiseRaw) return;
42 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
43 if (!IS_SPEC_FUNCTION(resolver))
44 throw MakeTypeError('resolver_not_a_function', [resolver]);
45 var promise = PromiseInit(this);
47 %DebugPushPromise(promise, Promise);
48 resolver(function(x) { PromiseResolve(promise, x) },
49 function(r) { PromiseReject(promise, r) });
51 PromiseReject(promise, e);
57 // Core functionality.
59 function PromiseSet(promise, status, value, onResolve, onReject) {
60 SET_PRIVATE(promise, promiseStatus, status);
61 SET_PRIVATE(promise, promiseValue, value);
62 SET_PRIVATE(promise, promiseOnResolve, onResolve);
63 SET_PRIVATE(promise, promiseOnReject, onReject);
64 if (DEBUG_IS_ACTIVE) {
65 %DebugPromiseEvent({ promise: promise, status: status, value: value });
70 function PromiseCreateAndSet(status, value) {
71 var promise = new $Promise(promiseRaw);
72 // If debug is active, notify about the newly created promise first.
73 if (DEBUG_IS_ACTIVE) PromiseSet(promise, 0, UNDEFINED);
74 return PromiseSet(promise, status, value);
77 function PromiseInit(promise) {
79 promise, 0, UNDEFINED, new InternalArray, new InternalArray)
82 function PromiseDone(promise, status, value, promiseQueue) {
83 if (GET_PRIVATE(promise, promiseStatus) === 0) {
84 var tasks = GET_PRIVATE(promise, promiseQueue);
85 if (tasks.length) PromiseEnqueue(value, tasks, status);
86 PromiseSet(promise, status, value);
90 function PromiseCoerce(constructor, x) {
91 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
96 return %_CallFunction(constructor, r, PromiseRejected);
98 if (IS_SPEC_FUNCTION(then)) {
99 var deferred = %_CallFunction(constructor, PromiseDeferred);
101 %_CallFunction(x, deferred.resolve, deferred.reject, then);
105 return deferred.promise;
111 function PromiseHandle(value, handler, deferred) {
113 %DebugPushPromise(deferred.promise, PromiseHandle);
114 DEBUG_PREPARE_STEP_IN_IF_STEPPING(handler);
115 var result = handler(value);
116 if (result === deferred.promise)
117 throw MakeTypeError('promise_cyclic', [result]);
118 else if (IsPromise(result))
119 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
121 deferred.resolve(result);
122 } catch (exception) {
123 try { deferred.reject(exception); } catch (e) { }
129 function PromiseEnqueue(value, tasks, status) {
130 var id, name, instrumenting = DEBUG_IS_ACTIVE;
131 %EnqueueMicrotask(function() {
133 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
135 for (var i = 0; i < tasks.length; i += 2) {
136 PromiseHandle(value, tasks[i], tasks[i + 1])
139 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
143 id = ++lastMicrotaskId;
144 name = status > 0 ? "Promise.resolve" : "Promise.reject";
145 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
149 function PromiseIdResolveHandler(x) { return x }
150 function PromiseIdRejectHandler(r) { throw r }
152 function PromiseNopResolver() {}
154 // -------------------------------------------------------------------
155 // Define exported functions.
159 IsPromise = function IsPromise(x) {
160 return IS_SPEC_OBJECT(x) && HAS_DEFINED_PRIVATE(x, promiseStatus);
163 PromiseCreate = function PromiseCreate() {
164 return new $Promise(PromiseNopResolver)
167 PromiseResolve = function PromiseResolve(promise, x) {
168 PromiseDone(promise, +1, x, promiseOnResolve)
171 PromiseReject = function PromiseReject(promise, r) {
172 // Check promise status to confirm that this reject has an effect.
173 // Call runtime for callbacks to the debugger or for unhandled reject.
174 if (GET_PRIVATE(promise, promiseStatus) == 0) {
175 var debug_is_active = DEBUG_IS_ACTIVE;
176 if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) {
177 %PromiseRejectEvent(promise, r, debug_is_active);
180 PromiseDone(promise, -1, r, promiseOnReject)
185 function PromiseDeferred() {
186 if (this === $Promise) {
187 // Optimized case, avoid extra closure.
188 var promise = PromiseInit(new $Promise(promiseRaw));
191 resolve: function(x) { PromiseResolve(promise, x) },
192 reject: function(r) { PromiseReject(promise, r) }
196 result.promise = new this(function(resolve, reject) {
197 result.resolve = resolve;
198 result.reject = reject;
204 function PromiseResolved(x) {
205 if (this === $Promise) {
206 // Optimized case, avoid extra closure.
207 return PromiseCreateAndSet(+1, x);
209 return new this(function(resolve, reject) { resolve(x) });
213 function PromiseRejected(r) {
215 if (this === $Promise) {
216 // Optimized case, avoid extra closure.
217 promise = PromiseCreateAndSet(-1, r);
218 // The debug event for this would always be an uncaught promise reject,
219 // which is usually simply noise. Do not trigger that debug event.
220 %PromiseRejectEvent(promise, r, false);
222 promise = new this(function(resolve, reject) { reject(r) });
229 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a.
231 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
232 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
233 var deferred = %_CallFunction(this.constructor, PromiseDeferred);
234 switch (GET_PRIVATE(this, promiseStatus)) {
236 throw MakeTypeError('not_a_promise', [this]);
238 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
239 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
242 PromiseEnqueue(GET_PRIVATE(this, promiseValue),
243 [onResolve, deferred],
247 if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) {
248 // Promise has already been rejected, but had no handler.
249 // Revoke previously triggered reject event.
250 %PromiseRevokeReject(this);
252 PromiseEnqueue(GET_PRIVATE(this, promiseValue),
253 [onReject, deferred],
257 // Mark this promise as having handler.
258 SET_PRIVATE(this, promiseHasHandler, true);
259 if (DEBUG_IS_ACTIVE) {
260 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
262 return deferred.promise;
265 PromiseCatch = function PromiseCatch(onReject) {
266 return this.then(UNDEFINED, onReject);
269 // Multi-unwrapped chaining with thenable coercion.
271 PromiseThen = function PromiseThen(onResolve, onReject) {
272 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
273 : PromiseIdResolveHandler;
274 onReject = IS_SPEC_FUNCTION(onReject) ? onReject
275 : PromiseIdRejectHandler;
277 var constructor = this.constructor;
278 return %_CallFunction(
281 x = PromiseCoerce(constructor, x);
283 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onReject);
284 return onReject(MakeTypeError('promise_cyclic', [x]));
285 } else if (IsPromise(x)) {
286 return x.then(onResolve, onReject);
288 DEBUG_PREPARE_STEP_IN_IF_STEPPING(onResolve);
299 function PromiseCast(x) {
300 // TODO(rossberg): cannot do better until we support @@create.
301 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
304 function PromiseAll(iterable) {
305 var deferred = %_CallFunction(this, PromiseDeferred);
306 var resolutions = [];
310 for (var value of iterable) {
311 this.resolve(value).then(
312 // Nested scope to get closure over current i.
313 // TODO(arv): Use an inner let binding once available.
317 if (--count === 0) deferred.resolve(resolutions);
320 function(r) { deferred.reject(r); });
326 deferred.resolve(resolutions);
332 return deferred.promise;
335 function PromiseRace(iterable) {
336 var deferred = %_CallFunction(this, PromiseDeferred);
338 for (var value of iterable) {
339 this.resolve(value).then(
340 function(x) { deferred.resolve(x) },
341 function(r) { deferred.reject(r) });
346 return deferred.promise;
350 // Utility for debugger
352 function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
353 var queue = GET_PRIVATE(promise, promiseOnReject);
354 if (IS_UNDEFINED(queue)) return false;
355 for (var i = 0; i < queue.length; i += 2) {
356 if (queue[i] != PromiseIdRejectHandler) return true;
357 if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) {
364 // Return whether the promise will be handled by a user-defined reject
365 // handler somewhere down the promise chain. For this, we do a depth-first
366 // search for a reject handler that's not the default PromiseIdRejectHandler.
367 PromiseHasUserDefinedRejectHandler =
368 function PromiseHasUserDefinedRejectHandler() {
369 return PromiseHasUserDefinedRejectHandlerRecursive(this);
372 // -------------------------------------------------------------------
373 // Install exported functions.
375 %CheckIsBootstrapping();
376 %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM);
378 $Promise.prototype, symbolToStringTag, "Promise", DONT_ENUM | READ_ONLY);
379 InstallFunctions($Promise, DONT_ENUM, [
380 "defer", PromiseDeferred,
381 "accept", PromiseResolved,
382 "reject", PromiseRejected,
385 "resolve", PromiseCast
387 InstallFunctions($Promise.prototype, DONT_ENUM, [
388 "chain", PromiseChain,
390 "catch", PromiseCatch