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
13 var $Promise = Promise;
16 //-------------------------------------------------------------------
18 // Core functionality.
20 // Status values: 0 = pending, +1 = resolved, -1 = rejected
21 var promiseStatus = GLOBAL_PRIVATE("Promise#status");
22 var promiseValue = GLOBAL_PRIVATE("Promise#value");
23 var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
24 var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
25 var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
27 function IsPromise(x) {
28 return IS_SPEC_OBJECT(x) && %HasLocalProperty(x, promiseStatus);
31 function Promise(resolver) {
32 if (resolver === promiseRaw) return;
33 if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
34 if (typeof resolver !== 'function')
35 throw MakeTypeError('resolver_not_a_function', [resolver]);
36 var promise = PromiseInit(this);
38 %DebugPromiseHandlePrologue(function() { return promise });
39 resolver(function(x) { PromiseResolve(promise, x) },
40 function(r) { PromiseReject(promise, r) });
42 PromiseReject(promise, e);
44 %DebugPromiseHandleEpilogue();
48 function PromiseSet(promise, status, value, onResolve, onReject) {
49 SET_PRIVATE(promise, promiseStatus, status);
50 SET_PRIVATE(promise, promiseValue, value);
51 SET_PRIVATE(promise, promiseOnResolve, onResolve);
52 SET_PRIVATE(promise, promiseOnReject, onReject);
56 function PromiseInit(promise) {
57 return PromiseSet(promise, 0, UNDEFINED, new InternalArray, new InternalArray)
60 function PromiseDone(promise, status, value, promiseQueue) {
61 if (GET_PRIVATE(promise, promiseStatus) === 0) {
62 PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
63 PromiseSet(promise, status, value);
67 function PromiseResolve(promise, x) {
68 PromiseDone(promise, +1, x, promiseOnResolve)
71 function PromiseReject(promise, r) {
72 PromiseDone(promise, -1, r, promiseOnReject)
78 function PromiseNopResolver() {}
80 function PromiseCreate() {
81 return new Promise(PromiseNopResolver)
87 function PromiseDeferred() {
88 if (this === $Promise) {
89 // Optimized case, avoid extra closure.
90 var promise = PromiseInit(new Promise(promiseRaw));
93 resolve: function(x) { PromiseResolve(promise, x) },
94 reject: function(r) { PromiseReject(promise, r) }
98 result.promise = new this(function(resolve, reject) {
99 result.resolve = resolve;
100 result.reject = reject;
106 function PromiseResolved(x) {
107 if (this === $Promise) {
108 // Optimized case, avoid extra closure.
109 return PromiseSet(new Promise(promiseRaw), +1, x);
111 return new this(function(resolve, reject) { resolve(x) });
115 function PromiseRejected(r) {
116 if (this === $Promise) {
117 // Optimized case, avoid extra closure.
118 return PromiseSet(new Promise(promiseRaw), -1, r);
120 return new this(function(resolve, reject) { reject(r) });
127 function PromiseIdResolveHandler(x) { return x }
128 function PromiseIdRejectHandler(r) { throw r }
130 function PromiseChain(onResolve, onReject) { // a.k.a. flatMap
131 onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
132 onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
133 var deferred = %_CallFunction(this.constructor, PromiseDeferred);
134 switch (GET_PRIVATE(this, promiseStatus)) {
136 throw MakeTypeError('not_a_promise', [this]);
138 GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
139 GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
142 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
145 PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
148 return deferred.promise;
151 function PromiseCatch(onReject) {
152 return this.then(UNDEFINED, onReject);
155 function PromiseEnqueue(value, tasks) {
156 EnqueueMicrotask(function() {
157 for (var i = 0; i < tasks.length; i += 2) {
158 PromiseHandle(value, tasks[i], tasks[i + 1])
163 function PromiseHandle(value, handler, deferred) {
165 %DebugPromiseHandlePrologue(
167 var queue = GET_PRIVATE(deferred.promise, promiseOnReject);
168 return (queue && queue.length == 0) ? deferred.promise : UNDEFINED;
170 var result = handler(value);
171 if (result === deferred.promise)
172 throw MakeTypeError('promise_cyclic', [result]);
173 else if (IsPromise(result))
174 %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
176 deferred.resolve(result);
177 } catch (exception) {
179 %DebugPromiseHandlePrologue(function() { return deferred.promise });
180 deferred.reject(exception);
181 } catch (e) { } finally {
182 %DebugPromiseHandleEpilogue();
185 %DebugPromiseHandleEpilogue();
190 // Multi-unwrapped chaining with thenable coercion.
192 function PromiseThen(onResolve, onReject) {
194 IS_NULL_OR_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
196 IS_NULL_OR_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
198 var constructor = this.constructor;
199 return %_CallFunction(
202 x = PromiseCoerce(constructor, x);
203 return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
204 IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
211 PromiseCoerce.table = new $WeakMap;
213 function PromiseCoerce(constructor, x) {
214 if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
219 var promise = %_CallFunction(constructor, r, PromiseRejected);
220 PromiseCoerce.table.set(x, promise);
223 if (typeof then === 'function') {
224 if (PromiseCoerce.table.has(x)) {
225 return PromiseCoerce.table.get(x);
227 var deferred = %_CallFunction(constructor, PromiseDeferred);
228 PromiseCoerce.table.set(x, deferred.promise);
230 %_CallFunction(x, deferred.resolve, deferred.reject, then);
234 return deferred.promise;
244 function PromiseCast(x) {
245 // TODO(rossberg): cannot do better until we support @@create.
246 return IsPromise(x) ? x : new this(function(resolve) { resolve(x) });
249 function PromiseAll(values) {
250 var deferred = %_CallFunction(this, PromiseDeferred);
251 var resolutions = [];
252 if (!%_IsArray(values)) {
253 deferred.reject(MakeTypeError('invalid_argument'));
254 return deferred.promise;
257 var count = values.length;
259 deferred.resolve(resolutions);
261 for (var i = 0; i < values.length; ++i) {
262 this.resolve(values[i]).then(
265 if (--count === 0) deferred.resolve(resolutions);
266 }.bind(UNDEFINED, i), // TODO(rossberg): use let loop once available
267 function(r) { deferred.reject(r) }
274 return deferred.promise;
277 function PromiseOne(values) {
278 var deferred = %_CallFunction(this, PromiseDeferred);
279 if (!%_IsArray(values)) {
280 deferred.reject(MakeTypeError('invalid_argument'));
281 return deferred.promise;
284 for (var i = 0; i < values.length; ++i) {
285 this.resolve(values[i]).then(
286 function(x) { deferred.resolve(x) },
287 function(r) { deferred.reject(r) }
293 return deferred.promise;
296 //-------------------------------------------------------------------
298 function SetUpPromise() {
299 %CheckIsBootstrapping();
300 %SetProperty(global, 'Promise', $Promise, DONT_ENUM);
301 InstallFunctions($Promise, DONT_ENUM, [
302 "defer", PromiseDeferred,
303 "accept", PromiseResolved,
304 "reject", PromiseRejected,
307 "resolve", PromiseCast
309 InstallFunctions($Promise.prototype, DONT_ENUM, [
310 "chain", PromiseChain,
312 "catch", PromiseCatch
318 // Functions to expose promise details to the debugger.
319 function GetPromiseStatus(promise) {
320 return GET_PRIVATE(promise, promiseStatus);
323 function GetPromiseValue(promise) {
324 return GET_PRIVATE(promise, promiseValue);