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
22 // mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
23 // if we could move these property names into the closure below.
24 // TODO(jkummerow/rossberg/yangguo): Find a better solution.
26 // Status values: 0 = pending, +1 = resolved, -1 = rejected
27 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
28 var promiseValue = GLOBAL_PRIVATE("Promise#value");
29 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
30 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
31 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
35 var $Promise = function Promise(resolver) {
36 if (resolver === promiseRaw) return;
37 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
38 if (!IS_SPEC_FUNCTION(resolver))
39 throw MakeTypeError('resolver_not_a_function', [resolver]);
40 var promise = PromiseInit(this);
42 %DebugPromiseHandlePrologue(function() { return promise });
43 resolver(function(x) { PromiseResolve(promise, x) },
44 function(r) { PromiseReject(promise, r) });
46 PromiseReject(promise, e);
48 %DebugPromiseHandleEpilogue();
52 // Core functionality.
54 function PromiseSet(promise, status, value, onResolve, onReject) {
55 SET_PRIVATE(promise, promiseStatus, status);
56 SET_PRIVATE(promise, promiseValue, value);
57 SET_PRIVATE(promise, promiseOnResolve, onResolve);
58 SET_PRIVATE(promise, promiseOnReject, onReject);
62 function PromiseInit(promise) {
64 promise, 0, UNDEFINED, new InternalArray, new InternalArray)
67 function PromiseDone(promise, status, value, promiseQueue) {
68 if (GET_PRIVATE(promise, promiseStatus) === 0) {
69 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
70 PromiseSet(promise, status, value);
74 function PromiseCoerce(constructor, x) {
75 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
80 return %_CallFunction(constructor, r, PromiseRejected);
82 if (IS_SPEC_FUNCTION(then)) {
83 var deferred = %_CallFunction(constructor, PromiseDeferred);
85 %_CallFunction(x, deferred.resolve, deferred.reject, then);
89 return deferred.promise;
95 function PromiseHandle(value, handler, deferred) {
97 %DebugPromiseHandlePrologue(
99 var queue = GET_PRIVATE(deferred.promise, promiseOnReject);
100 return (queue && queue.length == 0) ? deferred.promise : UNDEFINED;
102 var result = handler(value);
103 if (result === deferred.promise)
104 throw MakeTypeError('promise_cyclic', [result]);
105 else if (IsPromise(result))
106 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
108 deferred.resolve(result);
109 } catch (exception) {
111 %DebugPromiseHandlePrologue(function() { return deferred.promise });
112 deferred.reject(exception);
113 } catch (e) { } finally {
114 %DebugPromiseHandleEpilogue();
117 %DebugPromiseHandleEpilogue();
121 function PromiseEnqueue(value, tasks) {
122 %EnqueueMicrotask(function() {
123 for (var i = 0; i < tasks.length; i += 2) {
124 PromiseHandle(value, tasks[i], tasks[i + 1])
129 function PromiseIdResolveHandler(x) { return x }
130 function PromiseIdRejectHandler(r) { throw r }
132 function PromiseNopResolver() {}
134 // -------------------------------------------------------------------
135 // Define exported functions.
139 IsPromise = function IsPromise(x) {
140 return IS_SPEC_OBJECT(x) && HAS_PRIVATE(x, promiseStatus);
143 PromiseCreate = function PromiseCreate() {
144 return new $Promise(PromiseNopResolver)
147 PromiseResolve = function PromiseResolve(promise, x) {
148 PromiseDone(promise, +1, x, promiseOnResolve)
151 PromiseReject = function PromiseReject(promise, r) {
152 PromiseDone(promise, -1, r, promiseOnReject)
157 function PromiseDeferred() {
158 if (this === $Promise) {
159 // Optimized case, avoid extra closure.
160 var promise = PromiseInit(new $Promise(promiseRaw));
163 resolve: function(x) { PromiseResolve(promise, x) },
164 reject: function(r) { PromiseReject(promise, r) }
168 result.promise = new this(function(resolve, reject) {
169 result.resolve = resolve;
170 result.reject = reject;
176 function PromiseResolved(x) {
177 if (this === $Promise) {
178 // Optimized case, avoid extra closure.
179 return PromiseSet(new $Promise(promiseRaw), +1, x);
181 return new this(function(resolve, reject) { resolve(x) });
185 function PromiseRejected(r) {
186 if (this === $Promise) {
187 // Optimized case, avoid extra closure.
188 return PromiseSet(new $Promise(promiseRaw), -1, r);
190 return new this(function(resolve, reject) { reject(r) });
196 PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a.
198 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
199 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
200 var deferred = %_CallFunction(this.constructor, PromiseDeferred);
201 switch (GET_PRIVATE(this, promiseStatus)) {
203 throw MakeTypeError('not_a_promise', [this]);
205 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
206 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
209 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
212 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
215 return deferred.promise;
218 PromiseCatch = function PromiseCatch(onReject) {
219 return this.then(UNDEFINED, onReject);
222 // Multi-unwrapped chaining with thenable coercion.
224 PromiseThen = function PromiseThen(onResolve, onReject) {
225 onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
226 : PromiseIdResolveHandler;
227 onReject = IS_SPEC_FUNCTION(onReject) ? onReject
228 : PromiseIdRejectHandler;
230 var constructor = this.constructor;
231 return %_CallFunction(
234 x = PromiseCoerce(constructor, x);
235 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
236 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
245 function PromiseCast(x) {
246 // TODO(rossberg): cannot do better until we support @@create.
247 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
250 function PromiseAll(values) {
251 var deferred = %_CallFunction(this, PromiseDeferred);
252 var resolutions = [];
253 if (!%_IsArray(values)) {
254 deferred.reject(MakeTypeError('invalid_argument'));
255 return deferred.promise;
258 var count = values.length;
260 deferred.resolve(resolutions);
262 for (var i = 0; i < values.length; ++i) {
263 this.resolve(values[i]).then(
266 if (--count === 0) deferred.resolve(resolutions);
267 }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once
269 function(r) { deferred.reject(r) }
276 return deferred.promise;
279 function PromiseOne(values) {
280 var deferred = %_CallFunction(this, PromiseDeferred);
281 if (!%_IsArray(values)) {
282 deferred.reject(MakeTypeError('invalid_argument'));
283 return deferred.promise;
286 for (var i = 0; i < values.length; ++i) {
287 this.resolve(values[i]).then(
288 function(x) { deferred.resolve(x) },
289 function(r) { deferred.reject(r) }
295 return deferred.promise;
298 // -------------------------------------------------------------------
299 // Install exported functions.
301 %CheckIsBootstrapping();
302 %SetProperty(global, 'Promise', $Promise, DONT_ENUM);
303 InstallFunctions($Promise, DONT_ENUM, [
304 "defer", PromiseDeferred,
305 "accept", PromiseResolved,
306 "reject", PromiseRejected,
309 "resolve", PromiseCast
311 InstallFunctions($Promise.prototype, DONT_ENUM, [
312 "chain", PromiseChain,
314 "catch", PromiseCatch