Skip to main content

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}