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;
23 // mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
24 // if we could move these property names into the closure below.
25 // TODO(jkummerow/rossberg/yangguo): Find a better solution.
27 // Status values: 0 = pending, +1 = resolved, -1 = rejected
28 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
29 var promiseValue = GLOBAL_PRIVATE("Promise#value");
30 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
31 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
32 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
33 var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
34 var lastMicrotaskId = 0;
38 var $Promise = function Promise(resolver) {
39 if (resolver === promiseRaw) return;
40 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
41 if (!IS_SPEC_FUNCTION(resolver))
42 throw MakeTypeError('resolver_not_a_function', [resolver]);
43 var promise = PromiseInit(this);
45 %DebugPushPromise(promise);
46 resolver(function(x) { PromiseResolve(promise, x) },
47 function(r) { PromiseReject(promise, r) });
49 PromiseReject(promise, e);
55 // Core functionality.
57 function PromiseSet(promise, status, value, onResolve, onReject) {
58 SET_PRIVATE(promise, promiseStatus, status);
59 SET_PRIVATE(promise, promiseValue, value);
60 SET_PRIVATE(promise, promiseOnResolve, onResolve);
61 SET_PRIVATE(promise, promiseOnReject, onReject);
62 if (DEBUG_IS_ACTIVE) {
63 %DebugPromiseEvent({ promise: promise, status: status, value: value });
68 function PromiseInit(promise) {
70 promise, 0, UNDEFINED, new InternalArray, new InternalArray)
73 function PromiseDone(promise, status, value, promiseQueue) {
74 if (GET_PRIVATE(promise, promiseStatus) === 0) {
75 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);
76 PromiseSet(promise, status, value);
80 function PromiseCoerce(constructor, x) {
81 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
86 return %_CallFunction(constructor, r, PromiseRejected);
88 if (IS_SPEC_FUNCTION(then)) {
89 var deferred = %_CallFunction(constructor, PromiseDeferred);
91 %_CallFunction(x, deferred.resolve, deferred.reject, then);
95 return deferred.promise;
101 function PromiseHandle(value, handler, deferred) {
103 %DebugPushPromise(deferred.promise);
104 var result = handler(value);
105 if (result === deferred.promise)
106 throw MakeTypeError('promise_cyclic', [result]);
107 else if (IsPromise(result))
108 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
110 deferred.resolve(result);
111 } catch (exception) {
112 try { deferred.reject(exception); } catch (e) { }
118 function PromiseEnqueue(value, tasks, status) {
119 var id, name, instrumenting = DEBUG_IS_ACTIVE;
120 %EnqueueMicrotask(function() {
122 %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
124 for (var i = 0; i < tasks.length; i += 2) {
125 PromiseHandle(value, tasks[i], tasks[i + 1])
128 %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
132 id = ++lastMicrotaskId;
133 name = status > 0 ? "Promise.resolve" : "Promise.reject";
134 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
138 function PromiseIdResolveHandler(x) { return x }
139 function PromiseIdRejectHandler(r) { throw r }
141 function PromiseNopResolver() {}
143 // -------------------------------------------------------------------
144 // Define exported functions.
148 IsPromise = function IsPromise(x) {
149 return IS_SPEC_OBJECT(x) && HAS_PRIVATE(x, promiseStatus);
152 PromiseCreate = function PromiseCreate() {
153 return new $Promise(PromiseNopResolver)
156 PromiseResolve = function PromiseResolve(promise, x) {
157 PromiseDone(promise, +1, x, promiseOnResolve)
160 PromiseReject = function PromiseReject(promise, r) {
161 // Check promise status to confirm that this reject has an effect.
162 // Check promiseDebug property to avoid duplicate event.
163 if (DEBUG_IS_ACTIVE &&
164 GET_PRIVATE(promise, promiseStatus) == 0 &&
165 !HAS_PRIVATE(promise, promiseDebug)) {
166 %DebugPromiseRejectEvent(promise, r);
168 PromiseDone(promise, -1, r, promiseOnReject)
173 function PromiseDeferred() {
174 if (this === $Promise) {
175 // Optimized case, avoid extra closure.
176 var promise = PromiseInit(new $Promise(promiseRaw));
179 resolve: function(x) { PromiseResolve(promise, x) },
180 reject: function(r) { PromiseReject(promise, r) }
184 result.promise = new this(function(resolve, reject) {
185 result.resolve = resolve;
186 result.reject = reject;
192 function PromiseResolved(x) {
193 if (this === $Promise) {
194 // Optimized case, avoid extra closure.
195 return PromiseSet(new $Promise(promiseRaw), +1, x);
197 return new this(function(resolve, reject) { resolve(x) });
201 function PromiseRejected(r) {
202 if (this === $Promise) {
203 // Optimized case, avoid extra closure.
204 return PromiseSet(new $Promise(promiseRaw), -1, r);
206 return new this(function(resolve, reject) { reject(r) });
212 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a.
214 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
215 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
216 var deferred = %_CallFunction(this.constructor, PromiseDeferred);
217 switch (GET_PRIVATE(this, promiseStatus)) {
219 throw MakeTypeError('not_a_promise', [this]);
221 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
222 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
225 PromiseEnqueue(GET_PRIVATE(this, promiseValue),
226 [onResolve, deferred],
230 PromiseEnqueue(GET_PRIVATE(this, promiseValue),
231 [onReject, deferred],
235 if (DEBUG_IS_ACTIVE) {
236 %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
238 return deferred.promise;
241 PromiseCatch = function PromiseCatch(onReject) {
242 return this.then(UNDEFINED, onReject);
245 // Multi-unwrapped chaining with thenable coercion.
247 PromiseThen = function PromiseThen(onResolve, onReject) {
248 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
249 : PromiseIdResolveHandler;
250 onReject = IS_SPEC_FUNCTION(onReject) ? onReject
251 : PromiseIdRejectHandler;
253 var constructor = this.constructor;
254 return %_CallFunction(
257 x = PromiseCoerce(constructor, x);
258 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
259 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
268 function PromiseCast(x) {
269 // TODO(rossberg): cannot do better until we support @@create.
270 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
273 function PromiseAll(values) {
274 var deferred = %_CallFunction(this, PromiseDeferred);
275 var resolutions = [];
276 if (!%_IsArray(values)) {
277 deferred.reject(MakeTypeError('invalid_argument'));
278 return deferred.promise;
281 var count = values.length;
283 deferred.resolve(resolutions);
285 for (var i = 0; i < values.length; ++i) {
286 this.resolve(values[i]).then(
288 // Nested scope to get closure over current i (and avoid .bind).
289 // TODO(rossberg): Use for-let instead once available.
292 resolutions[i_captured] = x;
293 if (--count === 0) deferred.resolve(resolutions);
296 function(r) { deferred.reject(r) }
303 return deferred.promise;
306 function PromiseOne(values) {
307 var deferred = %_CallFunction(this, PromiseDeferred);
308 if (!%_IsArray(values)) {
309 deferred.reject(MakeTypeError('invalid_argument'));
310 return deferred.promise;
313 for (var i = 0; i < values.length; ++i) {
314 this.resolve(values[i]).then(
315 function(x) { deferred.resolve(x) },
316 function(r) { deferred.reject(r) }
322 return deferred.promise;
326 // Utility for debugger
328 PromiseHasRejectHandler = function PromiseHasRejectHandler() {
329 // Mark promise as already having triggered a reject event.
330 SET_PRIVATE(this, promiseDebug, true);
331 var queue = GET_PRIVATE(this, promiseOnReject);
332 return !IS_UNDEFINED(queue) && queue.length > 0;
335 // -------------------------------------------------------------------
336 // Install exported functions.
338 %CheckIsBootstrapping();
339 %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM);
340 InstallFunctions($Promise, DONT_ENUM, [
341 "defer", PromiseDeferred,
342 "accept", PromiseResolved,
343 "reject", PromiseRejected,
346 "resolve", PromiseCast
348 InstallFunctions($Promise.prototype, DONT_ENUM, [
349 "chain", PromiseChain,
351 "catch", PromiseCatch