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.