Last active
July 11, 2020 08:48
-
-
Save rauschma/d68bc86187d6e1b5a6477c054e07c7fb to your computer and use it in GitHub Desktop.
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
/* | |
* 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