[JSC] Optimize Promise runtime functions
[WebKit-https.git] / Source / JavaScriptCore / builtins / PromiseConstructor.js
1 /*
2  * Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 function all(iterable)
27 {
28     "use strict";
29
30     if (!@isObject(this))
31         @throwTypeError("|this| is not an object");
32
33     var promiseCapability = @newPromiseCapability(this);
34
35     var values = [];
36     var index = 0;
37     var remainingElementsCount = 1;
38
39     function newResolveElement(index)
40     {
41         var alreadyCalled = false;
42         return function @resolve(argument)
43         {
44             if (alreadyCalled)
45                 return @undefined;
46             alreadyCalled = true;
47
48             @putByValDirect(values, index, argument);
49
50             --remainingElementsCount;
51             if (remainingElementsCount === 0)
52                 return promiseCapability.@resolve.@call(@undefined, values);
53
54             return @undefined;
55         }
56     }
57
58     try {
59         var promiseResolve = this.resolve;
60         if (typeof promiseResolve !== "function")
61             @throwTypeError("Promise resolve is not a function");
62
63         for (var value of iterable) {
64             @putByValDirect(values, index, @undefined);
65             var nextPromise = promiseResolve.@call(this, value);
66             var resolveElement = newResolveElement(index);
67             ++remainingElementsCount;
68             nextPromise.then(resolveElement, promiseCapability.@reject);
69             ++index;
70         }
71
72         --remainingElementsCount;
73         if (remainingElementsCount === 0)
74             promiseCapability.@resolve.@call(@undefined, values);
75     } catch (error) {
76         promiseCapability.@reject.@call(@undefined, error);
77     }
78
79     return promiseCapability.@promise;
80 }
81
82 function allSettled(iterable)
83 {
84     "use strict";
85
86     if (!@isObject(this))
87         @throwTypeError("|this| is not an object");
88
89     var promiseCapability = @newPromiseCapability(this);
90
91     var values = [];
92     var remainingElementsCount = 1;
93     var index = 0;
94
95     function newResolveRejectElements(index)
96     {
97         var alreadyCalled = false;
98
99         var resolveElement = function @resolve(x)
100         {
101             if (alreadyCalled)
102                 return @undefined;
103             alreadyCalled = true;
104
105             var obj = {
106                 status: "fulfilled",
107                 value: x
108             };
109
110             @putByValDirect(values, index, obj);
111
112             --remainingElementsCount;
113             if (remainingElementsCount === 0)
114                 return promiseCapability.@resolve.@call(@undefined, values);
115
116             return @undefined;
117         };
118
119         var rejectElement = function @reject(x)
120         {
121             if (alreadyCalled)
122                 return @undefined;
123             alreadyCalled = true;
124
125             var obj = {
126                 status: "rejected",
127                 reason: x
128             };
129
130             @putByValDirect(values, index, obj);
131
132             --remainingElementsCount;
133             if (remainingElementsCount === 0)
134                 return promiseCapability.@resolve.@call(@undefined, values);
135
136             return @undefined;
137         };
138
139         return [resolveElement, rejectElement];
140     }
141
142     try {
143         var promiseResolve = this.resolve;
144         if (typeof promiseResolve !== "function")
145             @throwTypeError("Promise resolve is not a function");
146
147         for (var value of iterable) {
148             @putByValDirect(values, index, @undefined);
149             var nextPromise = promiseResolve.@call(this, value);
150             var [resolveElement, rejectElement] = newResolveRejectElements(index);
151             ++remainingElementsCount;
152             nextPromise.then(resolveElement, rejectElement);
153             ++index;
154         }
155
156         --remainingElementsCount;
157         if (remainingElementsCount === 0)
158             promiseCapability.@resolve.@call(@undefined, values);
159     } catch (error) {
160         promiseCapability.@reject.@call(@undefined, error);
161     }
162
163     return promiseCapability.@promise;
164 }
165
166 function race(iterable)
167 {
168     "use strict";
169
170     if (!@isObject(this))
171         @throwTypeError("|this| is not an object");
172
173     var promiseCapability = @newPromiseCapability(this);
174
175     try {
176         var promiseResolve = this.resolve;
177         if (typeof promiseResolve !== "function")
178             @throwTypeError("Promise resolve is not a function");
179
180         for (var value of iterable) {
181             var nextPromise = promiseResolve.@call(this, value);
182             nextPromise.then(promiseCapability.@resolve, promiseCapability.@reject);
183         }
184     } catch (error) {
185         promiseCapability.@reject.@call(@undefined, error);
186     }
187
188     return promiseCapability.@promise;
189 }
190
191 function reject(reason)
192 {
193     "use strict";
194
195     if (!@isObject(this))
196         @throwTypeError("|this| is not an object");
197
198     if (this === @Promise) {
199         var promise = @newPromise();
200         @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, reason);
201         return promise;
202     }
203
204     return @promiseRejectSlow(this, reason);
205 }
206
207 function resolve(value)
208 {
209     "use strict";
210
211     if (!@isObject(this))
212         @throwTypeError("|this| is not an object");
213
214     if (@isPromise(value)) {
215         var valueConstructor = value.constructor;
216         if (valueConstructor === this)
217             return value;
218     }
219
220     if (this === @Promise) {
221         var promise = @newPromise();
222         @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, value);
223         return promise;
224     }
225
226     return @promiseResolveSlow(this, value);
227 }
228
229 @nakedConstructor
230 function Promise(executor)
231 {
232     "use strict";
233
234     if (typeof executor !== "function")
235         @throwTypeError("Promise constructor takes a function argument");
236
237     var promise = @createPromise(this, /* isInternalPromise */ false);
238     var capturedPromise = promise;
239
240     // FIXME: We should allow using function-declaration here.
241     // https://bugs.webkit.org/show_bug.cgi?id=203502
242     var @reject = function @reject(reason) {
243         return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
244     };
245
246     try {
247         executor(
248             function @resolve(resolution) {
249                 return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
250             }, @reject);
251     } catch (error) {
252         @reject(error);
253     }
254
255     return promise;
256 }
257
258 @nakedConstructor
259 function InternalPromise(executor)
260 {
261     "use strict";
262
263     if (typeof executor !== "function")
264         @throwTypeError("InternalPromise constructor takes a function argument");
265
266     var promise = @createPromise(this, /* isInternalPromise */ true);
267     var capturedPromise = promise;
268
269     // FIXME: We should allow using function-declaration here.
270     // https://bugs.webkit.org/show_bug.cgi?id=203502
271     var @reject = function @reject(reason) {
272         return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
273     };
274
275     try {
276         executor(
277             function @resolve(resolution) {
278                 return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
279             }, @reject);
280     } catch (error) {
281         @reject(error);
282     }
283
284     return promise;
285 }