1459747884c3de328cfcfc8cd3443724b7935d33
[WebKit-https.git] / Source / JavaScriptCore / builtins / AsyncGeneratorPrototype.js
1 /*
2  * Copyright (C) 2017 Oleksandr Skachkov <gskachkov@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 @globalPrivate
27 function asyncGeneratorQueueIsEmpty(generator)
28 {
29     "use strict";
30
31     return generator.@asyncGeneratorQueueLast === null;
32 }
33
34 @globalPrivate
35 function asyncGeneratorQueueEnqueue(generator, item)
36 {
37     "use strict";
38
39     @assert(item.@asyncGeneratorQueueItemNext === null && item.@asyncGeneratorQueueItemPrevious === null);
40
41     if (generator.@asyncGeneratorQueueFirst === null) {
42         @assert(generator.@asyncGeneratorQueueLast === null);
43
44         generator.@asyncGeneratorQueueFirst = item;
45         generator.@asyncGeneratorQueueLast = item;
46     } else {
47         item.@asyncGeneratorQueueItemPrevious = generator.@asyncGeneratorQueueLast;
48         generator.@asyncGeneratorQueueLast.@asyncGeneratorQueueItemNext = item;
49         generator.@asyncGeneratorQueueLast = item;
50     }
51 }
52
53 @globalPrivate
54 function asyncGeneratorQueueDequeue(generator)
55 {
56     "use strict";
57
58     if (generator.@asyncGeneratorQueueFirst === null)
59         return null;
60
61     const result = generator.@asyncGeneratorQueueFirst;
62     generator.@asyncGeneratorQueueFirst = result.@asyncGeneratorQueueItemNext;
63
64     if (generator.@asyncGeneratorQueueFirst === null)
65         generator.@asyncGeneratorQueueLast = null;
66
67     return result;
68 }
69
70 @globalPrivate
71 function asyncGeneratorDequeue(generator)
72 {
73     "use strict";
74
75     const queue = generator.@asyncGeneratorQueue;
76
77     @assert(!@asyncGeneratorQueueIsEmpty(generator), "Async genetator's Queue is an empty List.");
78     
79     return @asyncGeneratorQueueDequeue(generator);
80 }
81
82 @globalPrivate
83 function isExecutionState(generator)
84 {
85     "use strict";
86
87     return (generator.@generatorState > 0 && generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonNone)
88         || generator.@generatorState === @AsyncGeneratorStateExecuting
89         || generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonAwait;
90 }
91
92 @globalPrivate
93 function isSuspendYieldState(generator)
94 {
95     "use strict";
96
97     return (generator.@generatorState > 0 && generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonYield)
98         || generator.@generatorState === @AsyncGeneratorStateSuspendedYield;
99 }
100
101 @globalPrivate
102 function asyncGeneratorReject(generator, exception)
103 {
104     "use strict";
105
106     @assert(generator.@asyncIteratorSymbol !== @undefined, "Generator is not an AsyncGenerator instance.");
107
108     const { promiseCapability } = @asyncGeneratorDequeue(generator);
109     promiseCapability.@reject.@call(@undefined, exception);
110
111     return @asyncGeneratorResumeNext(generator);
112 }
113
114 @globalPrivate
115 function asyncGeneratorResolve(generator, value, done)
116 {
117     "use strict";
118
119     @assert(generator.@asyncIteratorSymbol !== @undefined, "Generator is not an AsyncGenerator instance.");
120
121     const { promiseCapability } = @asyncGeneratorDequeue(generator);
122     promiseCapability.@resolve.@call(@undefined, { done, value: value });
123
124     return @asyncGeneratorResumeNext(generator);
125 }
126
127 @globalPrivate
128 function asyncGeneratorYield(generator, value, resumeMode)
129 {
130     "use strict";
131
132     function asyncGeneratorYieldAwaited(result)
133     {
134         generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonYield;
135         @asyncGeneratorResolve(generator, result, false);
136     }
137
138     generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonAwait;
139
140     @awaitValue(generator, value, asyncGeneratorYieldAwaited);
141
142     return @undefined;
143 }
144
145 @globalPrivate
146 function awaitValue(generator, value, onFullfiled)
147 {
148     "use strict";
149
150     const wrappedValue = @newPromiseCapability(@Promise);
151
152     const onRejected = function (result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeThrow); };
153
154     wrappedValue.@resolve.@call(@undefined, value);
155     wrappedValue.@promise.@then(onFullfiled, onRejected);
156
157     return wrappedValue;
158 }
159
160 @globalPrivate
161 function doAsyncGeneratorBodyCall(generator, resumeValue, resumeMode)
162 {
163     "use strict";
164
165     let value = @undefined;
166     let state = generator.@generatorState;
167
168     generator.@generatorState = @AsyncGeneratorStateExecuting;
169     generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
170
171     try {
172         value = generator.@generatorNext.@call(generator.@generatorThis, generator, state, resumeValue, resumeMode, generator.@generatorFrame);
173         if (generator.@generatorState === @AsyncGeneratorStateExecuting)
174             generator.@generatorState = @AsyncGeneratorStateCompleted;
175     } catch (error) {
176         generator.@generatorState = @AsyncGeneratorStateCompleted;
177         generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
178
179         return @asyncGeneratorReject(generator, error);
180     }
181
182     if (generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonAwait) {
183         const onFulfilled = function(result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeNormal); };
184
185         @awaitValue(generator, value, onFulfilled);
186
187         return @undefined;
188     }
189
190     if (generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonYield)
191         return @asyncGeneratorYield(generator, value, resumeMode);
192
193     if (generator.@generatorState === @AsyncGeneratorStateCompleted) {
194         generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
195         return @asyncGeneratorResolve(generator, value, true);
196     }
197
198     return @undefined;
199 }
200
201 @globalPrivate
202 function asyncGeneratorResumeNext(generator)
203 {
204     "use strict";
205
206     @assert(generator.@asyncIteratorSymbol !== @undefined, "Generator is not an AsyncGenerator instance");
207
208     let state = generator.@generatorState;
209
210     @assert(state !== @AsyncGeneratorStateExecuting, "Async generator should not be in executing state");
211
212     if (state === @AsyncGeneratorStateAwaitingReturn)
213         return @undefined;
214
215     if (@asyncGeneratorQueueIsEmpty(generator))
216         return @undefined;
217
218     const next = generator.@asyncGeneratorQueueFirst;
219
220     if (next.resumeMode !== @GeneratorResumeModeNormal) {
221         if (state === @AsyncGeneratorStateSuspendedStart) {
222             generator.@generatorState = @AsyncGeneratorStateCompleted;
223             state = @AsyncGeneratorStateCompleted;
224         }
225
226         if (state === @AsyncGeneratorStateCompleted) {
227             if (next.resumeMode === @GeneratorResumeModeReturn) {
228                 generator.@generatorState = @AsyncGeneratorStateAwaitingReturn;
229
230                 const promiseCapability = @newPromiseCapability(@Promise);
231                 promiseCapability.@resolve.@call(@undefined, next.value);
232
233                 const throwawayCapabilityPromise = promiseCapability.@promise.@then(
234                     function (result) { generator.@generatorState = @AsyncGeneratorStateCompleted; @asyncGeneratorResolve(generator, result, true); },
235                     function (error) { generator.@generatorState = @AsyncGeneratorStateCompleted; @asyncGeneratorReject(generator, error); });
236
237                 throwawayCapabilityPromise.@promiseIsHandled = true;
238
239                 return @undefined;
240             }
241
242             @assert(next.resumeMode === @GeneratorResumeModeThrow, "Async generator has wrong mode");
243
244             return @asyncGeneratorReject(generator, next.value);;
245         }
246     } else if (state === @AsyncGeneratorStateCompleted)
247         return @asyncGeneratorResolve(generator, @undefined, true);
248
249     @assert(state === @AsyncGeneratorStateSuspendedStart || @isSuspendYieldState(generator), "Async generator has wrong state");
250
251     @doAsyncGeneratorBodyCall(generator, next.value, next.resumeMode);
252
253     return @undefined;
254 }
255
256 @globalPrivate
257 function asyncGeneratorEnqueue(generator, value, resumeMode)
258 {
259     "use strict";
260     
261     const promiseCapability = @newPromiseCapability(@Promise);
262     if (!@isObject(generator) || typeof generator.@asyncGeneratorSuspendReason !== 'number') {
263         promiseCapability.@reject.@call(@undefined, new @TypeError('|this| should be an async generator'));
264         return promiseCapability.@promise;
265     }
266
267     @asyncGeneratorQueueEnqueue(generator, {resumeMode, value, promiseCapability, @asyncGeneratorQueueItemNext: null, @asyncGeneratorQueueItemPrevious: null});
268
269     if (!@isExecutionState(generator))
270         @asyncGeneratorResumeNext(generator);
271
272     return promiseCapability.@promise;
273 }
274
275 function next(value)
276 {
277     "use strict";
278
279     return @asyncGeneratorEnqueue(this, value, @GeneratorResumeModeNormal);
280 }
281
282 function return(value)
283 {
284     "use strict";
285
286     return @asyncGeneratorEnqueue(this, value, @GeneratorResumeModeReturn);
287 }
288
289 function throw(exception)
290 {
291     "use strict";
292     
293     return @asyncGeneratorEnqueue(this, exception, @GeneratorResumeModeThrow);
294 }