Last active
February 16, 2021 12:47
-
-
Save rauschma/34487775b3c2d02ff7a5b82f42172073 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
/* | |
Proof of concept: Writing dual-mode (sync and async) code via generators | |
Recommendation: start by reading the example (at the end). | |
API: | |
– The API object is called `def`. | |
– Dual-mode `await`: const unwrapped = yield wrapped; | |
– Dual-mode `yield`: yield def.$one(singleValue) | |
– Dual-mode `yield*`: yield def.$all(iterable) | |
– Dual-mode `function`: def.func(function* (x, y) {}) | |
– Dual-mode `function*`: def.gen(function* (x, y) {}) | |
– Dual-mode `for-of`: yield* def.$forEach(iterable, function* (elem, index) {}); | |
– Yielding works inside “body” of loop! | |
– `break` via: return true | |
– Still missing: | |
– Support for Promise combinators, esp. for Promise.all(). | |
Other thoughts: | |
– Code will likely work like a parameterized module (think revealing module pattern): | |
– Parameter: an API object (that supports synchronous or asynchronous operation) | |
– Result: a single exported function or an object with exported functions | |
- This proof of concept works, but isn’t exactly pretty: | |
– I suspect it’s more practical to write async code and | |
create the sync version by removing `async` and `await`. | |
*/ | |
//========== Library ========== | |
const yieldOne = Symbol('yieldOne'); | |
const yieldAll = Symbol('yieldAll'); | |
const defCommon = { | |
$one(value) { | |
return {[yieldOne]: value}; | |
}, | |
$all(value) { | |
return {[yieldAll]: value}; | |
}, | |
/** | |
* Note: you can use `yield` inside the “body” of the loop | |
* just like you would outside the loop. | |
*/ | |
* $forEach(iterable, callback) { | |
const def = this; | |
let index = 0; | |
const iter = def.getIterator(iterable); | |
while (true) { | |
const {done, value} = yield iter.next(); | |
if (done) { | |
return; | |
} | |
const result = yield* callback(value, index); | |
if (result) { | |
// truthy = break from loop | |
return; | |
} | |
index++; | |
} | |
}, | |
}; | |
const defSync = { | |
...defCommon, | |
func(genFunc) { | |
return function (...args) { | |
const genObj = genFunc.apply(this, args); | |
let nextArg = null; | |
while (true) { | |
const {value, done} = genObj.next(nextArg); | |
if (done) return value; | |
if (value && {}.hasOwnProperty.call(value, yieldOne)) { | |
// `yield` used as `yield` | |
throw new Error('Can’t yield one from a sync function'); | |
} else if (value && {}.hasOwnProperty.call(value, yieldAll)) { | |
// `yield` used as `yield*` | |
throw new Error('Can’t yield all from a sync function'); | |
} else { | |
// `yield` used as `await` (synchronously) | |
nextArg = value; | |
} | |
} | |
}; | |
}, | |
gen(genFunc) { | |
return function* (...args) { | |
const genObj = genFunc.apply(this, args); | |
let nextArg = null; | |
while (true) { | |
const {value, done} = genObj.next(nextArg); | |
if (done) return value; | |
if (value && {}.hasOwnProperty.call(value, yieldOne)) { | |
// `yield` used as `yield` | |
yield value[yieldOne]; | |
nextArg = null; | |
} else if (value && {}.hasOwnProperty.call(value, yieldAll)) { | |
// `yield` used as `yield*` | |
yield* value[yieldAll]; | |
nextArg = null; | |
} else { | |
// `yield` used as `await` (synchronously) | |
nextArg = value; | |
} | |
} | |
}; | |
}, | |
getIterator(iterable) { | |
return iterable[Symbol.iterator](); | |
} | |
}; | |
const defAsync = { | |
...defCommon, | |
func(genFunc) { | |
return async function (...args) { | |
const genObj = genFunc.apply(this, args); | |
let nextArg = null; | |
while (true) { | |
const {value, done} = genObj.next(nextArg); | |
if (done) return value; | |
if (value && {}.hasOwnProperty.call(value, yieldOne)) { | |
// `yield` used as `yield` | |
throw new Error('Can’t yield one from an async function'); | |
} else if (value && {}.hasOwnProperty.call(value, yieldAll)) { | |
// `yield` used as `yield*` | |
throw new Error('Can’t yield all from an async function'); | |
} else { | |
// `yield` used as `await` | |
nextArg = await value; | |
} | |
} | |
}; | |
}, | |
gen(genFunc) { | |
return async function* (...args) { | |
const genObj = genFunc.apply(this, args); | |
let nextArg = null; | |
while (true) { | |
const {value, done} = genObj.next(nextArg); | |
if (done) return value; | |
if (value && {}.hasOwnProperty.call(value, yieldOne)) { | |
// `yield` used as `yield` | |
yield value[yieldOne]; | |
nextArg = null; | |
} else if (value && {}.hasOwnProperty.call(value, yieldAll)) { | |
// `yield` used as `yield*` | |
yield* value[yieldAll]; | |
nextArg = null; | |
} else { | |
// `yield` used as `await` | |
nextArg = await value; | |
} | |
} | |
}; | |
}, | |
getIterator(iterable) { | |
return iterable[Symbol.asyncIterator](); | |
} | |
}; | |
//========== Example: using the library ========== | |
const def = defSync; | |
// const def = defAsync; | |
const generateValues = def.gen(function* () { | |
yield def.$one('a'); | |
yield def.$one('b'); | |
yield def.$one('c'); | |
yield def.$one('d'); | |
yield def.$one('e'); | |
}); | |
const pairValues = def.gen(function* (iterable) { | |
const iter = def.getIterator(iterable); | |
while (true) { | |
const {done: done1, value: input1} = yield iter.next(); | |
if (done1) { | |
return; | |
} | |
const {done: done2, value: input2} = yield iter.next(); | |
if (done2) { | |
yield def.$one(input1); | |
return; | |
} | |
yield def.$one(input1 + ' ' + input2); | |
} | |
}); | |
const logValues = def.func(function* (iterable) { | |
yield* def.$forEach(iterable, function* (elem, index) { | |
console.log(index, elem); | |
}); | |
}); | |
logValues(pairValues(generateValues())); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment