54d81bce43791e6c5580a63b0b461dc8e5696594
[WebKit.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) && !!@getByIdDirectPrivate(promise, "promiseState");
35 }
36
37 @globalPrivate
38 function newPromiseReaction(capability, onFulfilled, onRejected)
39 {
40     "use strict";
41
42     return {
43         @capabilities: capability,
44         @onFulfilled: onFulfilled,
45         @onRejected: onRejected,
46     };
47 }
48
49 @globalPrivate
50 function newPromiseCapability(constructor)
51 {
52     "use strict";
53
54     if (!@isConstructor(constructor))
55         @throwTypeError("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             @throwTypeError("resolve function is already set");
67         if (promiseCapability.@reject !== @undefined)
68             @throwTypeError("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         @throwTypeError("executor did not take a resolve function");
78
79     if (typeof promiseCapability.@reject !== "function")
80         @throwTypeError("executor did not take a reject function");
81
82     promiseCapability.@promise = promise;
83
84     return promiseCapability;
85 }
86
87 @globalPrivate
88 function newHandledRejectedPromise(error)
89 {
90     "use strict";
91     let promise = @Promise.@reject(error);
92     @putByIdDirectPrivate(promise, "promiseIsHandled", true);
93     return promise;
94 }
95
96 @globalPrivate
97 function triggerPromiseReactions(state, reactions, argument)
98 {
99     "use strict";
100
101     for (var index = 0, length = reactions.length; index < length; ++index)
102         @enqueueJob(@promiseReactionJob, [state, reactions[index], argument]);
103 }
104
105 @globalPrivate
106 function rejectPromise(promise, reason)
107 {
108     "use strict";
109
110     var reactions = @getByIdDirectPrivate(promise, "promiseReactions");
111     @putByIdDirectPrivate(promise, "promiseResult", reason);
112     @putByIdDirectPrivate(promise, "promiseReactions", @undefined);
113     @putByIdDirectPrivate(promise, "promiseState", @promiseStateRejected);
114
115     @InspectorInstrumentation.promiseRejected(promise, reason, reactions);
116
117     if (!@getByIdDirectPrivate(promise, "promiseIsHandled"))
118         @hostPromiseRejectionTracker(promise, @promiseRejectionReject);
119
120     @triggerPromiseReactions(@promiseStateRejected, reactions, reason);
121 }
122
123 @globalPrivate
124 function fulfillPromise(promise, value)
125 {
126     "use strict";
127
128     var reactions = @getByIdDirectPrivate(promise, "promiseReactions");
129     @putByIdDirectPrivate(promise, "promiseResult", value);
130     @putByIdDirectPrivate(promise, "promiseReactions", @undefined);
131     @putByIdDirectPrivate(promise, "promiseState", @promiseStateFulfilled);
132
133     @InspectorInstrumentation.promiseFulfilled(promise, value, reactions);
134
135     @triggerPromiseReactions(@promiseStateFulfilled, reactions, value);
136 }
137
138 @globalPrivate
139 function createResolvingFunctions(promise)
140 {
141     "use strict";
142
143     var alreadyResolved = false;
144
145     function @resolve(resolution) {
146         if (alreadyResolved)
147             return @undefined;
148         alreadyResolved = true;
149
150         if (resolution === promise)
151             return @rejectPromise(promise, @makeTypeError("Resolve a promise with itself"));
152
153         if (!@isObject(resolution))
154             return @fulfillPromise(promise, resolution);
155
156         var then;
157         try {
158             then = resolution.then;
159         } catch (error) {
160             return @rejectPromise(promise, error);
161         }
162
163         if (typeof then !== 'function')
164             return @fulfillPromise(promise, resolution);
165
166         @enqueueJob(@promiseResolveThenableJob, [promise, resolution, then]);
167
168         return @undefined;
169     }
170
171     function @reject(reason) {
172         if (alreadyResolved)
173             return @undefined;
174         alreadyResolved = true;
175
176         return @rejectPromise(promise, reason);
177     }
178
179     return { @resolve, @reject };
180 }
181
182 @globalPrivate
183 function promiseReactionJob(state, reaction, argument)
184 {
185     "use strict";
186
187     var promiseCapability = reaction.@capabilities;
188
189     var result;
190     var handler = (state === @promiseStateFulfilled) ? reaction.@onFulfilled: reaction.@onRejected;
191     try {
192         result = handler(argument);
193     } catch (error) {
194         return promiseCapability.@reject.@call(@undefined, error);
195     }
196
197     return promiseCapability.@resolve.@call(@undefined, result);
198 }
199
200 @globalPrivate
201 function promiseResolveThenableJob(promiseToResolve, thenable, then)
202 {
203     "use strict";
204
205     var resolvingFunctions = @createResolvingFunctions(promiseToResolve);
206
207     try {
208         return then.@call(thenable, resolvingFunctions.@resolve, resolvingFunctions.@reject);
209     } catch (error) {
210         return resolvingFunctions.@reject.@call(@undefined, error);
211     }
212 }
213
214 @globalPrivate
215 function initializePromise(executor)
216 {
217     "use strict";
218
219     if (typeof executor !== 'function')
220         @throwTypeError("Promise constructor takes a function argument");
221
222     @putByIdDirectPrivate(this, "promiseState", @promiseStatePending);
223     @putByIdDirectPrivate(this, "promiseReactions", []);
224     @putByIdDirectPrivate(this, "promiseIsHandled", false);
225
226     var resolvingFunctions = @createResolvingFunctions(this);
227     try {
228         executor(resolvingFunctions.@resolve, resolvingFunctions.@reject);
229     } catch (error) {
230         return resolvingFunctions.@reject.@call(@undefined, error);
231     }
232
233     return this;
234 }