JavaScript Generators Tutorial - Section 3: Advanced Patterns
Async generators combine generators with async/await, allowing you to yield Promises and handle asynchronous data streams.
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
// Consume with for-await-of
(async () => {
for await (const value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
})();
// Function declaration
async function* myAsyncGen() {
yield 1;
yield await fetchData();
}
// Function expression
const myAsyncGen = async function* () {
yield 1;
yield await fetchData();
};
// Arrow function (not supported)
// const myAsyncGen = async* () => {}; // ❌ Syntax error
// Object method
const obj = {
async *myAsyncGen() {
yield 1;
yield await fetchData();
}
};
// Class method
class MyClass {
async *myAsyncGen() {
yield 1;
yield await fetchData();
}
}
async function* numbers() {
for (let i = 1; i <= 3; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
for await (const num of numbers()) {
console.log(num); // 1 (after 1s), 2 (after 2s), 3 (after 3s)
}
})();
async function* fetchPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
// Usage
(async () => {
for await (const items of fetchPages('/api/users')) {
console.log('Page:', items);
// Process each page as it arrives
}
})();
async function* readFileInChunks(file, chunkSize = 1024) {
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
const text = await chunk.text();
yield text;
offset += chunkSize;
}
}
// Usage
(async () => {
const file = new File(['Hello World...'], 'test.txt');
for await (const chunk of readFileInChunks(file, 5)) {
console.log('Chunk:', chunk);
}
})();
async function* eventStream(eventTarget, eventName) {
const queue = [];
let resolve;
const listener = event => {
if (resolve) {
resolve(event);
resolve = null;
} else {
queue.push(event);
}
};
eventTarget.addEventListener(eventName, listener);
try {
while (true) {
if (queue.length > 0) {
yield queue.shift();
} else {
yield await new Promise(r => resolve = r);
}
}
} finally {
eventTarget.removeEventListener(eventName, listener);
}
}
// Usage
(async () => {
const button = document.querySelector('#myButton');
for await (const event of eventStream(button, 'click')) {
console.log('Button clicked!', event);
if (event.detail === 3) break; // Stop after 3 clicks
}
})();
async function* fetchUsers() {
const response = await fetch('/api/users');
const users = await response.json();
for (const user of users) {
yield user;
}
}
async function* enrichUsers(userStream) {
for await (const user of userStream) {
const details = await fetch(`/api/users/${user.id}/details`);
const enriched = await details.json();
yield { ...user, ...enriched };
}
}
async function* filterActiveUsers(userStream) {
for await (const user of userStream) {
if (user.active) {
yield user;
}
}
}
// Compose pipeline
(async () => {
const users = fetchUsers();
const enriched = enrichUsers(users);
const active = filterActiveUsers(enriched);
for await (const user of active) {
console.log('Active user:', user);
}
})();
async function* queryDatabase(query) {
const cursor = await db.collection('users').find(query);
while (await cursor.hasNext()) {
yield await cursor.next();
}
await cursor.close();
}
// Usage
(async () => {
for await (const user of queryDatabase({ age: { $gt: 18 } })) {
console.log(user);
}
})();
async function* withErrorHandling() {
try {
yield await fetch('/api/data1');
yield await fetch('/api/data2');
yield await fetch('/api/data3');
} catch (error) {
console.error('Error:', error);
yield { error: true, message: error.message };
} finally {
console.log('Cleanup');
}
}
(async () => {
for await (const data of withErrorHandling()) {
console.log(data);
}
})();
function* syncGen() {
yield 1;
yield 2;
yield 3;
}
async function* asyncWrapper(syncGenerator) {
for (const value of syncGenerator) {
await new Promise(resolve => setTimeout(resolve, 100));
yield value;
}
}
(async () => {
for await (const value of asyncWrapper(syncGen())) {
console.log(value); // 1, 2, 3 (with delays)
}
})();
async function* websocketStream(url) {
const ws = new WebSocket(url);
const queue = [];
let resolve;
ws.onmessage = event => {
if (resolve) {
resolve(event.data);
resolve = null;
} else {
queue.push(event.data);
}
};
await new Promise(r => ws.onopen = r);
try {
while (ws.readyState === WebSocket.OPEN) {
if (queue.length > 0) {
yield queue.shift();
} else {
yield await new Promise(r => resolve = r);
}
}
} finally {
ws.close();
}
}
// Usage
(async () => {
for await (const message of websocketStream('ws://localhost:8080')) {
console.log('Received:', message);
}
})();
async function* for async generatorsfor-await-of loopsNow let's explore practical use cases for generators in real applications!