It should be easy to add a private global helper function for builtins
[WebKit-https.git] / Source / JavaScriptCore / builtins / PromiseOperations.js
1 /*
2  * Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
3  * Copyright (C) 2016 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 // @internal
28
29 @globalPrivate
30 function isPromise(promise)
31 {
32     "use strict";
33
34     return @isObject(promise) && !!promise.@promiseState;
35 }
36
37 @globalPrivate
38 function newPromiseReaction(capability, handler)
39 {
40     "use strict";
41
42     return {
43         @capabilities: capability,
44         @handler: handler
45     };
46 }
47
48 @globalPrivate
49 function newPromiseCapability(constructor)
50 {
51     "use strict";
52
53     // FIXME: Check isConstructor(constructor).
54     if (typeof constructor !== "function")
55         throw new @TypeError("promise capability requires a constructor function");
56
57     var promiseCapability = {
58         @promise: @undefined,
59         @resolve: @undefined,
60         @reject: @undefined
61     };
62
63     function executor(resolve, reject)
64     {
65         if (promiseCapability.@resolve !== @undefined)
66             throw new @TypeError("resolve function is already set");
67         if (promiseCapability.@reject !== @undefined)
68             throw new @TypeError("reject function is already set");
69
70         promiseCapability.@resolve = resolve;
71         promiseCapability.@reject = reject;
72     }
73
74     var promise = new constructor(executor);
75
76     if (typeof promiseCapability.@resolve !== "function")
77         throw new @TypeError("executor did not take a resolve function");
78
79     if (typeof promiseCapability.@reject !== "function")
80         throw new @TypeError("executor did not take a reject function");
81
82     promiseCapability.@promise = promise;
83
84     return promiseCapability;
85 }
86
87 @globalPrivate
88 function triggerPromiseReactions(reactions, argument)
89 {
90     "use strict";
91
92     for (var index = 0, length = reactions.length; index < length; ++index)
93         @enqueueJob(@promiseReactionJob, [reactions[index], argument]);
94 }
95
96 @globalPrivate
97 function rejectPromise(promise, reason)
98 {
99     "use strict";
100
101     var reactions = promise.@promiseRejectReactions;
102     promise.@promiseResult = reason;
103     promise.@promiseFulfillReactions = @undefined;
104     promise.@promiseRejectReactions = @undefined;
105     promise.@promiseState = @promiseStateRejected;
106
107     @InspectorInstrumentation.promiseRejected(promise, reason, reactions);
108
109     @triggerPromiseReactions(reactions, reason);
110 }
111
112 @globalPrivate
113 function fulfillPromise(promise, value)
114 {
115     "use strict";
116
117     var reactions = promise.@promiseFulfillReactions;
118     promise.@promiseResult = value;
119     promise.@promiseFulfillReactions = @undefined;
120     promise.@promiseRejectReactions = @undefined;
121     promise.@promiseState = @promiseStateFulfilled;
122
123     @InspectorInstrumentation.promiseFulfilled(promise, value, reactions);
124
125     @triggerPromiseReactions(reactions, value);
126 }
127
128 @globalPrivate
129 function createResolvingFunctions(promise)
130 {
131     "use strict";
132
133     var alreadyResolved = false;
134
135     var resolve = function (resolution) {
136         if (alreadyResolved)
137             return @undefined;
138         alreadyResolved = true;
139
140         if (resolution === promise)
141             return @rejectPromise(promise, new @TypeError("Resolve a promise with itself"));
142
143         if (!@isObject(resolution))
144             return @fulfillPromise(promise, resolution);
145
146         var then;
147         try {
148             then = resolution.then;
149         } catch (error) {
150             return @rejectPromise(promise, error);
151         }
152
153         if (typeof then !== 'function')
154             return @fulfillPromise(promise, resolution);
155
156         @enqueueJob(@promiseResolveThenableJob, [promise, resolution, then]);
157
158         return @undefined;
159     };
160
161     var reject = function (reason) {
162         if (alreadyResolved)
163             return @undefined;
164         alreadyResolved = true;
165
166         return @rejectPromise(promise, reason);
167     };
168
169     return {
170         @resolve: resolve,
171         @reject: reject
172     };
173 }
174
175 @globalPrivate
176 function promiseReactionJob(reaction, argument)
177 {
178     "use strict";
179
180     var promiseCapability = reaction.@capabilities;
181
182     var result;
183     try {
184         result = reaction.@handler.@call(@undefined, argument);
185     } catch (error) {
186         return promiseCapability.@reject.@call(@undefined, error);
187     }
188
189     return promiseCapability.@resolve.@call(@undefined, result);
190 }
191
192 @globalPrivate
193 function promiseResolveThenableJob(promiseToResolve, thenable, then)
194 {
195     "use strict";
196
197     var resolvingFunctions = @createResolvingFunctions(promiseToResolve);
198
199     try {
200         return then.@call(thenable, resolvingFunctions.@resolve, resolvingFunctions.@reject);
201     } catch (error) {
202         return resolvingFunctions.@reject.@call(@undefined, error);
203     }
204 }
205
206 @globalPrivate
207 function initializePromise(executor)
208 {
209     "use strict";
210
211     if (typeof executor !== 'function')
212         throw new @TypeError("Promise constructor takes a function argument");
213
214     this.@promiseState = @promiseStatePending;
215     this.@promiseFulfillReactions = [];
216     this.@promiseRejectReactions = [];
217
218     var resolvingFunctions = @createResolvingFunctions(this);
219     try {
220         executor(resolvingFunctions.@resolve, resolvingFunctions.@reject);
221     } catch (error) {
222         return resolvingFunctions.@reject.@call(@undefined, error);
223     }
224
225     return this;
226 }