Last active
July 13, 2022 03:40
-
-
Save nybblr/3af62797052c42f7090b4f8614b5e157 to your computer and use it in GitHub Desktop.
3 examples of using Async Generators and Async Iteration in JavaScript!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Create a Promise that resolves after ms time | |
var timer = function(ms) { | |
return new Promise(resolve => { | |
setTimeout(resolve, ms); | |
}); | |
}; | |
// Repeatedly generate a number starting | |
// from 0 after a random amount of time | |
var source = async function*() { | |
var i = 0; | |
while (true) { | |
await timer(Math.random() * 1000); | |
yield i++; | |
} | |
}; | |
// Return a new async iterator that applies a | |
// transform to the values from another async generator | |
var map = async function*(stream, transform) { | |
for await (let n of stream) { | |
yield transform(n); | |
} | |
}; | |
// Tie everything together | |
var run = async function() { | |
var stream = source(); | |
// Square values generated by source() as they arrive | |
stream = map(stream, n => n * n); | |
for await (let n of stream) { | |
console.log(n); | |
} | |
}; | |
run(); | |
// => 0 | |
// => 1 | |
// => 4 | |
// => 9 | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generate a Promise that listens only once for an event | |
var oncePromise = (emitter, event) => { | |
return new Promise(resolve => { | |
var handler = (...args) => { | |
emitter.removeEventListener(event, handler); | |
resolve(...args); | |
}; | |
emitter.addEventListener(event, handler); | |
}); | |
}; | |
// Add an async iterator to all WebSockets | |
WebSocket.prototype[Symbol.asyncIterator] = async function*() { | |
while(this.readyState !== 3) { | |
yield (await oncePromise(this, 'message')).data; | |
} | |
}; | |
// Tie everything together | |
var run = async () => { | |
var ws = new WebSocket('ws://localhost:3000/'); | |
for await (let message of ws) { | |
console.log(message); | |
} | |
}; | |
run(); | |
// => "hello" | |
// => "sandwich" | |
// => "otters" | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Tie everything together | |
var run = async () => { | |
var i = 0; | |
var clicks = streamify('click', document.querySelector('body')); | |
clicks = filter(clicks, e => e.target.matches('a')); | |
clicks = distinct(clicks, e => e.target); | |
clicks = map(clicks, e => [i++, e]); | |
clicks = throttle(clicks, 500); | |
subscribe(clicks, ([ id, click ]) => { | |
console.log(id); | |
console.log(click); | |
click.preventDefault(); | |
}); | |
}; | |
// Turn any event emitter into a stream | |
var streamify = async function*(event, element) { | |
while (true) { | |
yield await oncePromise(element, event); | |
} | |
}; | |
// Generate a Promise that listens only once for an event | |
var oncePromise = (emitter, event) => { | |
return new Promise(resolve => { | |
var handler = (...args) => { | |
emitter.removeEventListener(event, handler); | |
resolve(...args); | |
}; | |
emitter.addEventListener(event, handler); | |
}); | |
}; | |
// Only pass along events that meet a condition | |
var filter = async function*(stream, test) { | |
for await (var event of stream) { | |
if (test(event)) { | |
yield event; | |
} | |
} | |
}; | |
// Transform every event of the stream | |
var map = async function*(stream, transform) { | |
for await (var event of stream) { | |
yield transform(event); | |
} | |
}; | |
// Only pass along event if some time has passed since the last one | |
var throttle = async function*(stream, delay) { | |
var lastTime; | |
var thisTime; | |
for await (var event of stream) { | |
thisTime = (new Date()).getTime(); | |
if (!lastTime || thisTime - lastTime > delay) { | |
lastTime = thisTime; | |
yield event; | |
} | |
} | |
}; | |
var identity = e => e; | |
// Only pass along events that differ from the last one | |
var distinct = async function*(stream, extract = identity) { | |
var lastVal; | |
var thisVal; | |
for await (var event of stream) { | |
thisVal = extract(event); | |
if (thisVal !== lastVal) { | |
lastVal = thisVal; | |
yield event; | |
} | |
} | |
}; | |
// Invoke a callback every time an event arrives | |
var subscribe = async (stream, callback) => { | |
for await (var event of stream) { | |
callback(event); | |
} | |
}; | |
run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Okay... pretty sure this
debounce
should work as expected... will debounce initial event, and the last event afterinterval
ms pass... I find that for events such as resize, scroll and drag/zoom that debounce like below is the most appropriate filter.https://gist.github.com/tracker1/fb103312c276a585bdfa4565427692cb