One of the more recent additions to the Javascript specification is the asynchronous generator protocol. This is an especially useful pattern when you want to consume data off a socket/serial port etc., because it lets you do something like so:
for await (const buffer of readable) {
await writable.write(buffer);
}
Which is pretty cool, but not a huge improvement on the `pipe` functionality already exposed in Node streams.
Where it really shines is the ability to also yield observations, allowing you to build an observer pattern:
async * download(writable) {
await this.open();
try {
const readable = this.readSectors(...);
let counter = 0;
for await (const buffer of readable) {
const buffer = SECTOR.parse(buffer);
await writable.write(buffer);
counter++;
yield counter;
}
} finally {
await this.close();
}
}
The primary advantage is the flatness makes our exit handling very obvious. Similarly in readSectors it flattens the entry and exit of the read mode.
Those building React/Redux apps probably want to get those observations into their state. This is relatively easily achieved in redux-saga through the eventChannel API.
function asyncToChannel(generator) {
return eventChannel(emit => {
// Set up a promise that iterates the async iterator and emits
// events on the channel from it.
(async () => {
for await (const elem of generator) {
emit(elem);
}
emit(STOP_ITERATION);
})();
return () => {
generator.return();
};
});
}
// Saga triggered on DOWNLOAD_BEGIN
function* downloadSaga(action) {
const writable = ...
const channel = asyncToChannel(action.data.download(writable));
// Consume the channel into Redux actions
while (true) {
const progress = yield take(channel);
if (progress === STOP_ITERATION) break;
yield put(downloadProgress(action.data, progress));
}
console.debug("Download complete");
yield put(downloadComplete(action.data));
}
Tricky to work out, but much more easily read than callback hell.
