JavaScript Async Iteration & for-await-of Quiz

JavaScript
0 Passed
0% acceptance

Answer 35 questions covering the async iterator protocol, Symbol.asyncIterator, async generator syntax, yield vs yield*, promise handling, for-await-of mechanics, error management, streaming data scenarios, and using async generators with fetch.

35 Questions
~70 minutes
1

Question 1

What does the async iterator protocol require next() to return?

A
A Promise that resolves to an object with value and done properties
B
A synchronous object with value/done only
C
Any primitive without wrapping
D
A DOM node
2

Question 2

What prints?

javascript
const asyncIterable = {
  data: [10, 20],
  [Symbol.asyncIterator]() {
    let index = 0
    return {
      next: () => new Promise((resolve) => {
        setTimeout(() => {
          if (index < this.data.length) {
            resolve({ value: this.data[index++], done: false })
          } else {
            resolve({ value: undefined, done: true })
          }
        }, 10)
      })
    }
  }
}
const iterator = asyncIterable[Symbol.asyncIterator]()
iterator.next().then(console.log)
iterator.next().then(console.log)
iterator.next().then(console.log)
A
{ value: 10, done: false }, { value: 20, done: false }, { value: undefined, done: true } (logged asynchronously)
B
{ value: undefined, done: true } only
C
Throws because async iterables cannot use setTimeout
D
Logs promises without resolving
3

Question 3

How do synchronous and asynchronous iterables differ?

A
Async iterables expose Symbol.asyncIterator returning a promise-based iterator, while sync iterables use Symbol.iterator with immediate values
B
Async iterables cannot be consumed by loops
C
Sync iterables must use await
D
Async iterables require DOM APIs
4

Question 4

What prints?

javascript
async function consume(iterable) {
  const iterator = iterable[Symbol.asyncIterator]()
  console.log(await iterator.next())
  console.log(await iterator.next())
}
consume({
  count: 0,
  [Symbol.asyncIterator]() {
    return {
      next: () => Promise.resolve({ value: this.count++, done: this.count > 2 })
    }
  }
})
A
{value:0,done:false}, {value:1,done:false}
B
{value:1,done:true} twice
C
{value:undefined,done:true} immediately
D
Throws because Symbol.asyncIterator cannot return an object literal
5

Question 5

Why does Symbol.asyncIterator exist alongside Symbol.iterator?

A
It signals that iteration yields promises, so consumers know to await each result
B
It replaces Symbol.iterator entirely
C
It enforces synchronous execution
D
It only works in Node but not browsers
6

Question 6

What prints?

javascript
async function* fetchNumbers() {
  yield 1
  yield 2
}
const iterator = fetchNumbers()
iterator.next().then(console.log)
iterator.next().then(console.log)
iterator.next().then(console.log)
A
{value:1,done:false}, {value:2,done:false}, {value:undefined,done:true}
B
{value:Promise,done:false} always
C
Throws because async generators cannot yield numbers
D
{value:undefined,done:true} repeated from start
7

Question 7

Why use async function* instead of async function returning an array?

A
Async generators deliver values lazily as they become available, avoiding large buffering
B
They run faster in all cases
C
They disallow await expressions
D
They are required for every async function
8

Question 8

Which statement about async generators is true?

A
They can await inside the body, pausing between chunks and resuming when await resolves
B
They cannot use try/finally
C
They produce synchronous iterators
D
They cannot yield objects
9

Question 9

What prints?

javascript
async function* letters() {
  yield* ['a', 'b']
  yield 'c'
}
(async () => {
  for await (const char of letters()) {
    console.log(char)
  }
})()
A
a \n b \n c
B
c \n b \n a
C
Only a
D
Throws because yield* cannot use arrays in async generators
10

Question 10

When delegating with yield* to another async generator, what must the delegate expose?

A
Symbol.asyncIterator returning async iterator results
B
Symbol.iterator only
C
A Node.js stream
D
A DOM element
11

Question 11

What prints?

javascript
async function* outer() {
  yield 'start'
  yield* inner()
  return 'done'
}
async function* inner() {
  yield await Promise.resolve('middle')
}
(async () => {
  const iterator = outer()
  console.log(await iterator.next())
  console.log(await iterator.next())
  console.log(await iterator.next())
})()
A
{value:'start',done:false}, {value:'middle',done:false}, {value:'done',done:true}
B
{value:'start',done:false}, {value:undefined,done:true}, {value:undefined,done:true}
C
{value:'middle',done:false} repeated
D
Throws because async generators cannot return values
12

Question 12

Why must for-await-of await each promise before continuing?

A
Because async iterators signal readiness via promises; ignoring await would proceed before data arrives
B
Because promises throw if not awaited
C
Because for-await-of rewrites to callbacks
D
Because it only runs in browsers
13

Question 13

What prints?

javascript
async function* delayedRange() {
  for (let i = 1; i <= 3; i++) {
    await new Promise((resolve) => setTimeout(resolve, 5))
    yield i
  }
}
(async () => {
  for await (const value of delayedRange()) {
    console.log(value)
  }
})()
A
1, 2, 3 (each logged after a delay)
B
3, 2, 1
C
Immediately 1,2,3 with no delay
D
Throws because await cannot be inside for loops
14

Question 14

What prints?

javascript
async function consume(iterable) {
  for await (const value of iterable) {
    console.log(value)
  }
}
consume({
  async *[Symbol.asyncIterator]() {
    yield Promise.resolve('hello')
    yield 'world'
  }
})
A
hello \n world
B
Promise { "hello" } only
C
world \n hello
D
Throws because yield cannot emit promises
15

Question 15

Which loops can consume async iterables natively?

A
for-await-of loops introduced in ES2018
B
while loops only
C
for...of, automatically awaiting values
D
Array.prototype.map
16

Question 16

What prints?

javascript
async function log(iterable) {
  for await (const value of iterable) {
    console.log(value)
  }
}
log({
  count: 0,
  [Symbol.asyncIterator]() {
    return {
      next: () => Promise.resolve(
        this.count < 2
          ? { value: this.count++, done: false }
          : { value: undefined, done: true }
      )
    }
  }
})
A
0 then 1
B
Only 0
C
1 then 0
D
Throws immediately
17

Question 17

Why can for-await-of short-circuit early?

A
Breaking out of the loop stops calling next(), useful for partial consumption or early exit when data is sufficient
B
It is impossible to break from for-await-of
C
It automatically drains the iterator even after break
D
It only works with try/catch
18

Question 18

How does for-await-of handle rejections thrown by next()?

A
The rejection is propagated as an exception that can be caught via try/catch around the loop
B
Rejections are ignored
C
Rejections convert to undefined values
D
Rejections restart the iterator
19

Question 19

What prints?

javascript
async function* faulty() {
  yield 1
  throw new Error('boom')
}
(async () => {
  try {
    for await (const value of faulty()) {
      console.log(value)
    }
  } catch (error) {
    console.log('caught', error.message)
  }
})()
A
1 \n caught boom
B
caught boom only
C
1 \n boom (uncaught)
D
Throws because throw is invalid inside async generator
20

Question 20

What prints?

javascript
async function* resilient() {
  try {
    yield 1
    yield 2
  } finally {
    console.log('cleanup')
  }
}
(async () => {
  const iterator = resilient()
  console.log(await iterator.next())
  console.log(await iterator.return("done"))
})()
A
{value:1,done:false}, cleanup, {value:"done",done:true}
B
{value:1,done:false}, {value:2,done:false}
C
{value:undefined,done:true} twice
D
Throws because return() is unsupported
21

Question 21

Why is async iteration a good fit for streaming responses?

A
It lets you process chunks as soon as they arrive, reducing latency for long-running downloads
B
It forces the entire response to buffer first
C
It only works with synchronous data
D
It automatically retries failed requests
22

Question 22

What prints?

javascript
async function* paginator(fetchPage) {
  let page = 1
  while (true) {
    const result = await fetchPage(page)
    if (!result.items.length) return
    yield* result.items
    page += 1
  }
}
(async () => {
  const pages = [
    { items: ['A', 'B'] },
    { items: ['C'] },
    { items: [] }
  ]
  const iterator = paginator((index) => Promise.resolve(pages[index - 1]))
  for await (const item of iterator) {
    console.log(item)
  }
})()
A
A \n B \n C
B
A \n C \n B
C
Only A
D
Throws because yield* cannot be used in async generators
23

Question 23

What prints?

javascript
const queue = []
let resolvePending
const asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      next: () => {
        if (queue.length) {
          return Promise.resolve({ value: queue.shift(), done: false })
        }
        return new Promise((resolve) => {
          resolvePending = resolve
        })
      }
    }
  }
}
async function producer() {
  await new Promise((r) => setTimeout(r, 5))
  queue.push('first')
  resolvePending({ value: queue.shift(), done: false })
  resolvePending({ value: undefined, done: true })
}
(async () => {
  producer()
  for await (const item of asyncIterable) {
    console.log(item)
  }
})()
A
first
B
undefined
C
first twice
D
Throws because resolvePending is reassigned
24

Question 24

Why pair fetch with async generators?

A
You can iterate over paginated endpoints or streaming bodies without storing everything in memory
B
fetch cannot be used elsewhere
C
Async generators automatically retry failed fetches
D
They convert responses to GraphQL
25

Question 25

What prints?

javascript
async function* fetchPages(urls) {
  for (const url of urls) {
    const response = await Promise.resolve({ json: () => ({ url }) })
    yield response.json()
  }
}
(async () => {
  for await (const data of fetchPages(['/a', '/b'])) {
    console.log(data.url)
  }
})()
A
/a \n /b
B
/b \n /a
C
Only /a
D
Throws because json() cannot return plain objects
26

Question 26

What prints?

javascript
async function* chunkResponse(chunks) {
  for (const chunk of chunks) {
    yield await Promise.resolve(chunk.toUpperCase())
  }
}
(async () => {
  const responseChunks = ['ab', 'cd']
  let merged = ""
  for await (const chunk of chunkResponse(responseChunks)) {
    merged += chunk
  }
  console.log(merged)
})()
A
ABCD
B
abcd
C
AB
D
Throws because await cannot be used inside yield expression
27

Question 27

How does Symbol.asyncIterator enable interoperability with custom classes?

A
Any object implementing Symbol.asyncIterator can be consumed by for-await-of, regardless of internal representation
B
Classes must extend AsyncIterator
C
Symbol.asyncIterator is only for built-ins
D
It requires a prototype named AsyncIterable
28

Question 28

What prints?

javascript
class LogStream {
  constructor(messages) {
    this.messages = messages
  }
  async *[Symbol.asyncIterator]() {
    for (const message of this.messages) {
      await new Promise((r) => setTimeout(r, 2))
      yield message
    }
  }
}
(async () => {
  for await (const line of new LogStream(['info', 'warn'])) {
    console.log(line)
  }
})()
A
info \n warn
B
warn \n info
C
Only info
D
Throws because classes cannot define Symbol.asyncIterator
29

Question 29

What is the benefit of exposing both Symbol.iterator and Symbol.asyncIterator?

A
Consumers can choose sync or async iteration depending on context, improving compatibility
B
It speeds up GC
C
It is required by the spec
D
It ensures the iterable is cloned
30

Question 30

How does an async generator differ from simply returning an array of promises?

A
The generator yields one promise at a time and can incorporate backpressure or cancellation via return()/throw()
B
They are identical in all situations
C
Arrays automatically await promises sequentially
D
Generators cannot yield promises
31

Question 31

What prints?

javascript
async function* toAsync(iterable) {
  for (const value of iterable) {
    yield await Promise.resolve(value)
  }
}
(async () => {
  const iterator = toAsync([1, 2])
  console.log(await iterator.next())
  console.log(await iterator.next())
  console.log(await iterator.next())
})()
A
{value:1,done:false}, {value:2,done:false}, {value:undefined,done:true}
B
{value:Promise,done:false} repeated
C
{value:undefined,done:true} only
D
Throws because await cannot wrap synchronous values
32

Question 32

Why is backpressure easier to implement with async generators?

A
Producers only run when consumers await next(), naturally throttling production
B
Generators spawn threads automatically
C
Backpressure is impossible with async iteration
D
Generators prefetch all items
33

Question 33

What prints?

javascript
async function* cancellable() {
  try {
    yield 1
    await new Promise((resolve) => setTimeout(resolve, 100))
    yield 2
  } finally {
    console.log('closed')
  }
}
(async () => {
  const iterator = cancellable()
  console.log(await iterator.next())
  console.log(await iterator.return())
})()
A
{value:1,done:false}, closed, {value:undefined,done:true}
B
{value:1,done:false}, {value:2,done:false}
C
{value:undefined,done:true} twice without logging
D
Throws because return() cannot be awaited
34

Question 34

What prints?

javascript
async function* responder() {
  try {
    yield 'ping'
    yield await Promise.reject(new Error('fail'))
  } catch (error) {
    yield `caught ${error.message}`
  }
}
(async () => {
  const iterator = responder()
  console.log(await iterator.next())
  console.log(await iterator.next())
  console.log(await iterator.next())
})()
A
{value:'ping',done:false}, {value:'caught fail',done:false}, {value:undefined,done:true}
B
{value:'ping',done:false}, {value:undefined,done:true}, {value:undefined,done:true}
C
{value:undefined,done:true} repeated
D
Throws because catch cannot yield
35

Question 35

Why wrap async iterables with helper utilities (e.g., take, map, filter)?

A
They provide reusable building blocks for composing async pipelines without rewriting boilerplate loops
B
They force synchronous behavior
C
They replace fetch entirely
D
They prevent cancellation

QUIZZES IN JavaScript