boa_engine/builtins/async_generator/mod.rs
1//! Boa's implementation of ECMAScript's global `AsyncGenerator` object.
2//!
3//! More information:
4//! - [ECMAScript reference][spec]
5//!
6//! [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-objects
7
8use crate::{
9 Context, JsArgs, JsData, JsError, JsExpect, JsResult, JsString,
10 builtins::{
11 Promise,
12 generator::GeneratorContext,
13 iterable::create_iter_result_object,
14 promise::{PromiseCapability, if_abrupt_reject_promise},
15 },
16 context::intrinsics::Intrinsics,
17 error::JsNativeError,
18 js_string,
19 native_function::NativeFunction,
20 object::{CONSTRUCTOR, FunctionObjectBuilder, JsObject},
21 property::Attribute,
22 realm::Realm,
23 string::StaticJsStrings,
24 symbol::JsSymbol,
25 value::JsValue,
26 vm::{CompletionRecord, GeneratorResumeKind},
27};
28use boa_gc::{Finalize, Trace};
29use std::{collections::VecDeque, slice};
30
31use super::{BuiltInBuilder, IntrinsicObject};
32
33/// Indicates the state of an async generator.
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub(crate) enum AsyncGeneratorState {
36 SuspendedStart,
37 SuspendedYield,
38 Executing,
39 DrainingQueue,
40 Completed,
41}
42
43/// `AsyncGeneratorRequest Records`
44///
45/// More information:
46/// - [ECMAScript reference][spec]
47///
48/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records
49#[derive(Debug, Clone, Finalize, Trace)]
50pub(crate) struct AsyncGeneratorRequest {
51 /// The `[[Completion]]` slot.
52 pub(crate) completion: CompletionRecord,
53
54 /// The `[[Capability]]` slot.
55 capability: PromiseCapability,
56}
57
58/// The internal representation of an `AsyncGenerator` object.
59#[derive(Debug, Finalize, Trace, JsData)]
60pub struct AsyncGenerator {
61 /// The `[[AsyncGeneratorState]]` internal slot.
62 #[unsafe_ignore_trace]
63 pub(crate) state: AsyncGeneratorState,
64
65 /// The `[[AsyncGeneratorContext]]` internal slot.
66 pub(crate) context: Option<GeneratorContext>,
67
68 /// The `[[AsyncGeneratorQueue]]` internal slot.
69 pub(crate) queue: VecDeque<AsyncGeneratorRequest>,
70}
71
72impl IntrinsicObject for AsyncGenerator {
73 fn init(realm: &Realm) {
74 BuiltInBuilder::with_intrinsic::<Self>(realm)
75 .prototype(
76 realm
77 .intrinsics()
78 .objects()
79 .iterator_prototypes()
80 .async_iterator(),
81 )
82 .static_method(Self::next, js_string!("next"), 1)
83 .static_method(Self::r#return, js_string!("return"), 1)
84 .static_method(Self::throw, js_string!("throw"), 1)
85 .static_property(
86 JsSymbol::to_string_tag(),
87 Self::NAME,
88 Attribute::CONFIGURABLE,
89 )
90 .static_property(
91 CONSTRUCTOR,
92 realm
93 .intrinsics()
94 .constructors()
95 .async_generator_function()
96 .prototype(),
97 Attribute::CONFIGURABLE,
98 )
99 .build();
100 }
101
102 fn get(intrinsics: &Intrinsics) -> JsObject {
103 intrinsics.objects().async_generator()
104 }
105}
106
107impl AsyncGenerator {
108 const NAME: JsString = StaticJsStrings::ASYNC_GENERATOR;
109
110 /// `AsyncGenerator.prototype.next ( value )`
111 ///
112 /// More information:
113 /// - [ECMAScript reference][spec]
114 ///
115 /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next
116 pub(crate) fn next(
117 this: &JsValue,
118 args: &[JsValue],
119 context: &mut Context,
120 ) -> JsResult<JsValue> {
121 // 1. Let generator be the this value.
122 let generator = this;
123
124 // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
125 let promise_capability = PromiseCapability::new(
126 &context.intrinsics().constructors().promise().constructor(),
127 context,
128 )
129 .js_expect("cannot fail with promise constructor")?;
130
131 // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
132 // 4. IfAbruptRejectPromise(result, promiseCapability).
133 let result: JsResult<_> = generator.as_object().ok_or_else(|| {
134 JsNativeError::typ()
135 .with_message("generator resumed on non generator object")
136 .into()
137 });
138 let generator = if_abrupt_reject_promise!(result, promise_capability, context);
139 let result: JsResult<_> = generator.clone().downcast::<Self>().map_err(|_| {
140 JsNativeError::typ()
141 .with_message("generator resumed on non generator object")
142 .into()
143 });
144 let generator = if_abrupt_reject_promise!(result, promise_capability, context);
145
146 Self::inner_next(
147 &generator,
148 promise_capability,
149 args.get_or_undefined(0).clone(),
150 context,
151 )
152 .map(JsValue::from)
153 }
154
155 pub(crate) fn inner_next(
156 generator: &JsObject<AsyncGenerator>,
157 cap: PromiseCapability,
158 value: JsValue,
159 context: &mut Context,
160 ) -> JsResult<JsObject> {
161 // 5. Let state be generator.[[AsyncGeneratorState]].
162 let state = generator.borrow().data().state;
163
164 // 6. If state is completed, then
165 if state == AsyncGeneratorState::Completed {
166 // a. Let iteratorResult be CreateIterResultObject(undefined, true).
167 let iterator_result = create_iter_result_object(JsValue::undefined(), true, context);
168
169 // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
170 cap.resolve()
171 .call(&JsValue::undefined(), &[iterator_result], context)?;
172
173 // c. Return promiseCapability.[[Promise]].
174 return Ok(cap.promise);
175 }
176
177 // 7. Let completion be NormalCompletion(value).
178 let completion = CompletionRecord::Normal(value);
179
180 // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
181 Self::enqueue(generator, completion.clone(), cap.clone());
182
183 // 9. If state is either suspendedStart or suspendedYield, then
184 if state == AsyncGeneratorState::SuspendedStart
185 || state == AsyncGeneratorState::SuspendedYield
186 {
187 // a. Perform AsyncGeneratorResume(generator, completion).
188 Self::resume(generator, completion, context)?;
189 }
190
191 // 11. Return promiseCapability.[[Promise]].
192 Ok(cap.promise)
193 }
194
195 /// `AsyncGenerator.prototype.return ( value )`
196 ///
197 /// More information:
198 /// - [ECMAScript reference][spec]
199 ///
200 /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return
201 pub(crate) fn r#return(
202 this: &JsValue,
203 args: &[JsValue],
204 context: &mut Context,
205 ) -> JsResult<JsValue> {
206 // 1. Let generator be the this value.
207 let generator = this;
208
209 // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
210 let promise_capability = PromiseCapability::new(
211 &context.intrinsics().constructors().promise().constructor(),
212 context,
213 )
214 .js_expect("cannot fail with promise constructor")?;
215
216 // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
217 // 4. IfAbruptRejectPromise(result, promiseCapability).
218 let result: JsResult<_> = generator.as_object().ok_or_else(|| {
219 JsNativeError::typ()
220 .with_message("generator resumed on non generator object")
221 .into()
222 });
223 let generator_object = if_abrupt_reject_promise!(result, promise_capability, context);
224 let result: JsResult<_> = generator_object.clone().downcast::<Self>().map_err(|_| {
225 JsNativeError::typ()
226 .with_message("generator resumed on non generator object")
227 .into()
228 });
229 let generator = if_abrupt_reject_promise!(result, promise_capability, context);
230
231 Self::inner_return(
232 &generator,
233 promise_capability,
234 args.get_or_undefined(0).clone(),
235 context,
236 )
237 .map(JsValue::from)
238 }
239
240 pub(crate) fn inner_return(
241 generator: &JsObject<AsyncGenerator>,
242 cap: PromiseCapability,
243 return_value: JsValue,
244 context: &mut Context,
245 ) -> JsResult<JsObject> {
246 // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
247 let completion = CompletionRecord::Return(return_value.clone());
248
249 // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
250 Self::enqueue(generator, completion.clone(), cap.clone());
251
252 // 7. Let state be generator.[[AsyncGeneratorState]].
253 let state = generator.borrow().data().state;
254
255 // 8. If state is either suspended-start or completed, then
256 if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed {
257 // a. Set generator.[[AsyncGeneratorState]] to draining-queue.
258 generator.borrow_mut().data_mut().state = AsyncGeneratorState::DrainingQueue;
259
260 // b. Perform ! AsyncGeneratorAwaitReturn(generator).
261 Self::await_return(generator, return_value, context)?;
262 }
263 // 9. Else if state is suspended-yield, then
264 else if state == AsyncGeneratorState::SuspendedYield {
265 // a. Perform AsyncGeneratorResume(generator, completion).
266 Self::resume(generator, completion, context)?;
267 }
268 // 10. Else,
269 // a. Assert: state is either executing or draining-queue.
270
271 // 11. Return promiseCapability.[[Promise]].
272 Ok(cap.promise)
273 }
274
275 /// `AsyncGenerator.prototype.throw ( exception )`
276 ///
277 /// More information:
278 /// - [ECMAScript reference][spec]
279 ///
280 /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw
281 pub(crate) fn throw(
282 this: &JsValue,
283 args: &[JsValue],
284 context: &mut Context,
285 ) -> JsResult<JsValue> {
286 // 1. Let generator be the this value.
287 let generator = this;
288
289 // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
290 let promise_capability = PromiseCapability::new(
291 &context.intrinsics().constructors().promise().constructor(),
292 context,
293 )
294 .js_expect("cannot fail with promise constructor")?;
295
296 // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
297 // 4. IfAbruptRejectPromise(result, promiseCapability).
298 let result: JsResult<_> = generator.as_object().ok_or_else(|| {
299 JsNativeError::typ()
300 .with_message("generator resumed on non generator object")
301 .into()
302 });
303 let generator_object = if_abrupt_reject_promise!(result, promise_capability, context);
304 let result: JsResult<_> = generator_object.clone().downcast::<Self>().map_err(|_| {
305 JsNativeError::typ()
306 .with_message("generator resumed on non generator object")
307 .into()
308 });
309 let generator = if_abrupt_reject_promise!(result, promise_capability, context);
310 Self::inner_throw(
311 &generator,
312 promise_capability,
313 args.get_or_undefined(0).clone(),
314 context,
315 )
316 .map(JsValue::from)
317 }
318
319 pub(crate) fn inner_throw(
320 generator: &JsObject<AsyncGenerator>,
321 cap: PromiseCapability,
322 error_value: JsValue,
323 context: &mut Context,
324 ) -> JsResult<JsObject> {
325 let mut r#gen = generator.borrow_mut();
326
327 // 5. Let state be generator.[[AsyncGeneratorState]].
328 let mut state = r#gen.data().state;
329
330 // 6. If state is suspendedStart, then
331 if state == AsyncGeneratorState::SuspendedStart {
332 // a. Set generator.[[AsyncGeneratorState]] to completed.
333 r#gen.data_mut().state = AsyncGeneratorState::Completed;
334 r#gen.data_mut().context = None;
335
336 // b. Set state to completed.
337 state = AsyncGeneratorState::Completed;
338 }
339
340 drop(r#gen);
341
342 // 7. If state is completed, then
343 if state == AsyncGeneratorState::Completed {
344 // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
345 cap.reject().call(
346 &JsValue::undefined(),
347 slice::from_ref(&error_value),
348 context,
349 )?;
350
351 // b. Return promiseCapability.[[Promise]].
352 return Ok(cap.promise);
353 }
354
355 // 8. Let completion be ThrowCompletion(exception).
356 let completion = CompletionRecord::Throw(JsError::from_opaque(error_value));
357
358 // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
359 Self::enqueue(generator, completion.clone(), cap.clone());
360
361 // 10. If state is suspended-yield, then
362 if state == AsyncGeneratorState::SuspendedYield {
363 // a. Perform AsyncGeneratorResume(generator, completion).
364 Self::resume(generator, completion, context)?;
365 }
366
367 // 11. Else,
368 // a. Assert: state is either executing or draining-queue.
369
370 // 12. Return promiseCapability.[[Promise]].
371 Ok(cap.promise)
372 }
373
374 /// `AsyncGeneratorEnqueue ( generator, completion, promiseCapability )`
375 ///
376 /// More information:
377 /// - [ECMAScript reference][spec]
378 ///
379 /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
380 pub(crate) fn enqueue(
381 generator: &JsObject<AsyncGenerator>,
382 completion: CompletionRecord,
383 promise_capability: PromiseCapability,
384 ) {
385 let mut r#gen = generator.borrow_mut();
386 // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
387 let request = AsyncGeneratorRequest {
388 completion,
389 capability: promise_capability,
390 };
391
392 // 2. Append request to the end of generator.[[AsyncGeneratorQueue]].
393 r#gen.data_mut().queue.push_back(request);
394 }
395
396 /// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )`
397 ///
398 /// More information:
399 /// - [ECMAScript reference][spec]
400 ///
401 /// # Errors
402 ///
403 /// Returns `EngineError::Panic` if the async generator request queue of `generator` is empty.
404 ///
405 /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
406 pub(crate) fn complete_step(
407 generator: &JsObject<AsyncGenerator>,
408 completion: JsResult<JsValue>,
409 done: bool,
410 realm: Option<Realm>,
411 context: &mut Context,
412 ) -> JsResult<()> {
413 // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.
414 // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]].
415 // 3. Remove the first element from generator.[[AsyncGeneratorQueue]].
416 let next = generator
417 .borrow_mut()
418 .data_mut()
419 .queue
420 .pop_front()
421 .js_expect("1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.")?;
422
423 // 4. Let promiseCapability be next.[[Capability]].
424 let promise_capability = &next.capability;
425
426 // 5. Let value be completion.[[Value]].
427 match completion {
428 // 6. If completion is a throw completion, then
429 Err(e) => {
430 // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
431 promise_capability.reject().call(
432 &JsValue::undefined(),
433 &[e.into_opaque(context)?],
434 context,
435 )?;
436 }
437
438 // 7. Else,
439 Ok(value) => {
440 // a. Assert: completion is a normal completion.
441 // b. If realm is present, then
442 let iterator_result = if let Some(realm) = realm {
443 // i. Let oldRealm be the running execution context's Realm.
444 // ii. Set the running execution context's Realm to realm.
445 let old_realm = context.enter_realm(realm);
446
447 // iii. Let iteratorResult be CreateIteratorResultObject(value, done).
448 let iterator_result = create_iter_result_object(value, done, context);
449
450 // iv. Set the running execution context's Realm to oldRealm.
451 context.enter_realm(old_realm);
452
453 iterator_result
454 } else {
455 // c. Else,
456 // i. Let iteratorResult be CreateIteratorResultObject(value, done).
457 create_iter_result_object(value, done, context)
458 };
459
460 // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
461 promise_capability.resolve().call(
462 &JsValue::undefined(),
463 &[iterator_result],
464 context,
465 )?;
466 }
467 }
468 // 8. Return unused.
469 Ok(())
470 }
471
472 /// `AsyncGeneratorResume ( generator, completion )`
473 ///
474 /// More information:
475 /// - [ECMAScript reference][spec]
476 ///
477 /// # Errors
478 ///
479 /// Returns `EngineError::Panic` if `generator` is neither in the `SuspendedStart` nor in the `SuspendedYield` states.
480 ///
481 /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume
482 pub(crate) fn resume(
483 generator: &JsObject<AsyncGenerator>,
484 completion: CompletionRecord,
485 context: &mut Context,
486 ) -> JsResult<()> {
487 // 1. Assert: generator.[[AsyncGeneratorState]] is either suspended-start or suspended-yield.
488 assert!(matches!(
489 generator.borrow().data().state,
490 AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::SuspendedYield
491 ));
492
493 // 2. Let genContext be generator.[[AsyncGeneratorContext]].
494 let mut generator_context = generator
495 .borrow_mut()
496 .data_mut()
497 .context
498 .take()
499 .js_expect("generator context cannot be empty here")?;
500
501 // 5. Set generator.[[AsyncGeneratorState]] to executing.
502 generator.borrow_mut().data_mut().state = AsyncGeneratorState::Executing;
503
504 let (value, resume_kind) = match completion {
505 CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal),
506 CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return),
507 CompletionRecord::Throw(err) => (err.into_opaque(context)?, GeneratorResumeKind::Throw),
508 };
509
510 // 3. Let callerContext be the running execution context.
511 // 4. Suspend callerContext.
512 // 6. Push genContext onto the execution context stack; genContext is now the running execution context.
513 let result = generator_context.resume(Some(value), resume_kind, context);
514
515 // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
516 generator.borrow_mut().data_mut().context = Some(generator_context);
517
518 // 8. Assert: result is never an abrupt completion.
519 assert!(!result.is_throw_completion());
520
521 // 9. Assert: When we return here, genContext has already been removed from the execution context stack and
522 // callerContext is the currently running execution context.
523 // 10. Return unused.
524 Ok(())
525 }
526
527 /// `AsyncGeneratorAwaitReturn ( generator )`
528 ///
529 /// More information:
530 /// - [ECMAScript reference][spec]
531 ///
532 /// # Errors
533 ///
534 /// Returns `EngineError::Panic` if `generator` is not in the `DrainingQueue` state.
535 ///
536 /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
537 pub(crate) fn await_return(
538 generator: &JsObject<AsyncGenerator>,
539 value: JsValue,
540 context: &mut Context,
541 ) -> JsResult<()> {
542 // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
543 assert_eq!(
544 generator.borrow().data().state,
545 AsyncGeneratorState::DrainingQueue
546 );
547
548 // 2. Let queue be generator.[[AsyncGeneratorQueue]].
549 // 3. Assert: queue is not empty.
550 // 4. Let next be the first element of queue.
551 // 5. Let completion be Completion(next.[[Completion]]).
552 // 6. Assert: completion is a return completion.
553
554 // 7. Let promiseCompletion be Completion(PromiseResolve(%Promise%, completion.[[Value]])).
555 let promise_completion = Promise::promise_resolve(
556 &context.intrinsics().constructors().promise().constructor(),
557 value,
558 context,
559 );
560
561 let promise = match promise_completion {
562 Ok(value) => value
563 .downcast::<Promise>()
564 .ok()
565 .js_expect("%Promise% constructor must always return a Promise object")?,
566 // 8. If promiseCompletion is an abrupt completion, then
567 Err(e) => {
568 // a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true).
569 Self::complete_step(generator, Err(e), true, None, context)?;
570 // b. Perform AsyncGeneratorDrainQueue(generator).
571 Self::drain_queue(generator, context)?;
572 // c. Return unused.
573 return Ok(());
574 }
575 };
576
577 // 9. Assert: promiseCompletion is a normal completion.
578 // 10. Let promise be promiseCompletion.[[Value]].
579 // 11. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called:
580 // 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
581 let on_fulfilled = FunctionObjectBuilder::new(
582 context.realm(),
583 NativeFunction::from_copy_closure_with_captures(
584 |_this, args, generator, context| {
585 // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
586 assert_eq!(
587 generator.borrow().data().state,
588 AsyncGeneratorState::DrainingQueue
589 );
590
591 // b. Let result be NormalCompletion(value).
592 let result = Ok(args.get_or_undefined(0).clone());
593
594 // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
595 Self::complete_step(generator, result, true, None, context)?;
596
597 // d. Perform AsyncGeneratorDrainQueue(generator).
598 Self::drain_queue(generator, context)?;
599
600 // e. Return undefined.
601 Ok(JsValue::undefined())
602 },
603 generator.clone(),
604 ),
605 )
606 .name(js_string!())
607 .length(1)
608 .build();
609
610 // 13. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called:
611 // 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
612 let on_rejected = FunctionObjectBuilder::new(
613 context.realm(),
614 NativeFunction::from_copy_closure_with_captures(
615 |_this, args, generator, context| {
616 // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
617 assert_eq!(
618 generator.borrow().data().state,
619 AsyncGeneratorState::DrainingQueue
620 );
621
622 // b. Let result be ThrowCompletion(reason).
623 let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone()));
624
625 // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
626 Self::complete_step(generator, result, true, None, context)?;
627
628 // d. Perform AsyncGeneratorDrainQueue(generator).
629 Self::drain_queue(generator, context)?;
630
631 // e. Return undefined.
632 Ok(JsValue::undefined())
633 },
634 generator.clone(),
635 ),
636 )
637 .name(js_string!())
638 .length(1)
639 .build();
640
641 // 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
642 Promise::perform_promise_then(
643 &promise,
644 Some(on_fulfilled),
645 Some(on_rejected),
646 None,
647 context,
648 );
649
650 // 16. Return unused.
651 Ok(())
652 }
653
654 /// `AsyncGeneratorDrainQueue ( generator )`
655 ///
656 /// More information:
657 /// - [ECMAScript reference][spec]
658 ///
659 /// # Errors
660 ///
661 /// Returns `EngineError::Panic` if `generator` is not in the `DrainingQueue` state.
662 ///
663 /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
664 pub(crate) fn drain_queue(
665 generator: &JsObject<AsyncGenerator>,
666 context: &mut Context,
667 ) -> JsResult<()> {
668 // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
669 assert_eq!(
670 generator.borrow().data().state,
671 AsyncGeneratorState::DrainingQueue
672 );
673
674 // 2. Let queue be generator.[[AsyncGeneratorQueue]].
675 // 3. If queue is empty, then
676 if generator.borrow().data().queue.is_empty() {
677 // a. Set generator.[[AsyncGeneratorState]] to completed.
678 generator.borrow_mut().data_mut().state = AsyncGeneratorState::Completed;
679 generator.borrow_mut().data_mut().context = None;
680 // b. Return unused.
681 return Ok(());
682 }
683
684 // 4. Let done be false.
685 // 5. Repeat, while done is false,
686 loop {
687 // a. Let next be the first element of queue.
688 let next = generator
689 .borrow()
690 .data()
691 .queue
692 .front()
693 .js_expect("must have entry")?
694 .completion
695 .clone();
696
697 // b. Let completion be Completion(next.[[Completion]]).
698 match next {
699 // c. If completion is a return completion, then
700 CompletionRecord::Return(val) => {
701 // i. Perform AsyncGeneratorAwaitReturn(generator).
702 Self::await_return(generator, val, context)?;
703
704 // ii. Set done to true.
705 break;
706 }
707 // d. Else,
708 completion => {
709 // i. If completion is a normal completion, then
710 // 1. Set completion to NormalCompletion(undefined).
711 let completion = completion.consume().map(|_| JsValue::undefined());
712
713 // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
714 Self::complete_step(generator, completion, true, None, context)?;
715
716 // iii. If queue is empty, then
717 if generator.borrow().data().queue.is_empty() {
718 // 1. Set generator.[[AsyncGeneratorState]] to completed.
719 generator.borrow_mut().data_mut().state = AsyncGeneratorState::Completed;
720 generator.borrow_mut().data_mut().context = None;
721 // 2. Set done to true.
722 break;
723 }
724 }
725 }
726 }
727
728 // 6. Return unused.
729 Ok(())
730 }
731}