JavaScript Async Iteration & for-await-of Quiz
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.
Question 1
What does the async iterator protocol require next() to return?
Question 2
What prints?
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)
Question 3
How do synchronous and asynchronous iterables differ?
Question 4
What prints?
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 })
}
}
})
Question 5
Why does Symbol.asyncIterator exist alongside Symbol.iterator?
Question 6
What prints?
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)
Question 7
Why use async function* instead of async function returning an array?
Question 8
Which statement about async generators is true?
Question 9
What prints?
async function* letters() {
yield* ['a', 'b']
yield 'c'
}
(async () => {
for await (const char of letters()) {
console.log(char)
}
})()
Question 10
When delegating with yield* to another async generator, what must the delegate expose?
Question 11
What prints?
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())
})()
Question 12
Why must for-await-of await each promise before continuing?
Question 13
What prints?
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)
}
})()
Question 14
What prints?
async function consume(iterable) {
for await (const value of iterable) {
console.log(value)
}
}
consume({
async *[Symbol.asyncIterator]() {
yield Promise.resolve('hello')
yield 'world'
}
})
Question 15
Which loops can consume async iterables natively?
Question 16
What prints?
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 }
)
}
}
})
Question 17
Why can for-await-of short-circuit early?
Question 18
How does for-await-of handle rejections thrown by next()?
Question 19
What prints?
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)
}
})()
Question 20
What prints?
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"))
})()
Question 21
Why is async iteration a good fit for streaming responses?
Question 22
What prints?
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)
}
})()
Question 23
What prints?
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)
}
})()
Question 24
Why pair fetch with async generators?
Question 25
What prints?
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)
}
})()
Question 26
What prints?
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)
})()
Question 27
How does Symbol.asyncIterator enable interoperability with custom classes?
Question 28
What prints?
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)
}
})()
Question 29
What is the benefit of exposing both Symbol.iterator and Symbol.asyncIterator?
Question 30
How does an async generator differ from simply returning an array of promises?
Question 31
What prints?
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())
})()
Question 32
Why is backpressure easier to implement with async generators?
Question 33
What prints?
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())
})()
Question 34
What prints?
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())
})()
Question 35
Why wrap async iterables with helper utilities (e.g., take, map, filter)?
