Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active July 11, 2020 08:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rauschma/d68bc86187d6e1b5a6477c054e07c7fb to your computer and use it in GitHub Desktop.
Save rauschma/d68bc86187d6e1b5a6477c054e07c7fb to your computer and use it in GitHub Desktop.
/*
* Dual-mode (sync and async) code via channels (think CSP).
* Check out the example at the end.
*
* - In async mode, every `yield` is an `await`.
* - In sync mode, `yield` simply passes on the operand.
*
* More on CSP: https://2ality.com/2017/03/csp-vs-async-generators.html
*/
//########## Library: sync ##########
class SyncChannel {
_data = [];
_closed = false;
add(value) {
if (this.closed) {
throw new Error('Can’t add to closed channel');
}
this._data.unshift(value);
}
/** Related to `.add()` */
close() {
if (this.closed) {
throw new Error('Channel already closed');
}
this.closed = true;
}
next() {
if (this._data.length === 0) {
if (this.closed) {
return {
done: true,
};
}
throw new Error('No more data to read');
}
return {
done: false,
value: this._data.pop(),
};
}
}
function runSync(genObj) {
let argumentForNext = null;
while (true) {
const {value, done} = genObj.next(argumentForNext);
if (done) break;
argumentForNext = value;
}
}
//########## Library: async ##########
function createPromise() {
let resolve, reject;
const promise = new Promise((resolveFunc, rejectFunc) => {
resolve = resolveFunc;
reject = rejectFunc;
});
return {promise, resolve, reject};
}
/**
* States:
* - Empty
* - Filled
* - Closed
*/
class AsyncChannel {
_state = {kind: 'Empty'};
async add(value) {
if (this._state.kind === 'Filled') {
if (this._state.pendingWrite) {
throw new Error('Method called again without waiting for the result Promise to be settled.');
}
this._state.pendingWrite = createPromise();
await this._state.pendingWrite.promise;
}
switch (this._state.kind) {
case 'Empty':
const {pendingRead} = this._state;
this._state = {
kind: 'Filled',
value: value,
};
if (pendingRead) {
pendingRead.resolve();
}
return;
case 'Closed':
throw new Error('Can’t add to closed channel');
default:
throw new Error('Unsupported state: ' + this._state.kind);
}
}
/** Related to `.add()` */
async close() {
if (this._state.kind === 'Filled') {
if (this._state.pendingWrite) {
throw new Error('Method called again without waiting for the result Promise to be settled.');
}
this._state.pendingWrite = createPromise();
await this._state.pendingWrite.promise;
}
switch (this._state.kind) {
case 'Empty':
const {pendingRead} = this._state;
this._state = {
kind: 'Closed',
};
if (pendingRead) {
pendingRead.resolve();
}
return;
case 'Closed':
throw new Error('Channel already closed');
default:
throw new Error('Unsupported state: ' + this._state.kind);
}
}
async next() {
if (this._state.kind === 'Empty') {
if (this._state.pendingRead) {
throw new Error('Method called again without waiting for the result Promise to be settled.');
}
this._state.pendingRead = createPromise();
await this._state.pendingRead.promise;
}
switch (this._state.kind) {
case 'Filled':
const {pendingWrite, value}= this._state;
this._state = {
kind: 'Empty',
};
if (pendingWrite) {
pendingWrite.resolve();
}
return {
done: false,
value,
};
case 'Closed':
return {
done: true,
};
default:
throw new Error('Unsupported state: ' + this._state.kind);
}
}
}
async function runAsync(genObj) {
let argumentForNext = null;
while (true) {
const {value, done} = genObj.next(argumentForNext);
if (done) break;
argumentForNext = await value;
}
}
//########## Example ##########
const Channel = SyncChannel;
const run = runSync;
// const Channel = AsyncChannel;
// const run = runAsync;
function* numberLines(lines, output) {
let lineNumber = 1;
while (true) {
const {value, done} = yield lines.next();
if (done) {
output.close();
break;
}
yield output.add(lineNumber + ': ' + value);
lineNumber++;
}
}
function* logValues(values) {
while (true) {
const {value, done} = yield values.next();
if (done) break;
console.log(value);
}
console.log('DONE logging');
}
function main(lines) {
const output = new Channel();
run(numberLines(lines.values(), output));
run(logValues(output));
}
main(['a', 'b', 'c', 'd', ]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment