Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active February 16, 2021 12:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rauschma/34487775b3c2d02ff7a5b82f42172073 to your computer and use it in GitHub Desktop.
Save rauschma/34487775b3c2d02ff7a5b82f42172073 to your computer and use it in GitHub Desktop.
/*
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