4 ms·
You can use One Weird Trick with generator functions to make your code "generic" over synchronicity. I use this technique to avoid needing to implement both syn
by jitl 4y ago
You can use One Weird Trick with generator functions to make your code "generic" over synchronicity. I use this technique to avoid needing to implement both sync and async versions of some functions in my quickjs-emscripten library.
The great part about this technique as a library author is that unlike choosing to use a Promise return type, this technique is invisible in my public API. I can write a function like `export function coolAlgorithm(getData: (request: I) => O | Promise<O>): R | Promise<R>`, and we get automatic performance improvement if the caller's `getData` function happens to return synchronously, without mystery generator stuff showing up in the function signature.
Helper to make a function that can be either sync or async: https://github.com/justjake/quickjs-emscripten/blob/ff211447f403031cc7c2dcf484173a177cecf7a9/ts/asyncify-helpers.ts#L18-L26 https://github.com/justjake/quickjs-emscripten/blob/ff211447...
Uses: https://cs.github.com/justjake/quickjs-emscripten?q=yield*+language%3ATypeScript https://cs.github.com/justjake/quickjs-emscripten?q=yield*+l...
- athanagor2 4y agoI used a very similar pattern in a web app, in which the user creates objects by clicking in different places. There is a state built by each click, and it is convenient to just go back one or several steps behind sometimes. So you have something like const input1 = yield // arbitrary side effects, UI updates etc. const input2 = yield // ... Each time the user makes an input, it is accumulated in a list, and sent back to the generator function. When the user decides to cancel the last step, a generator is recreated and rerun, with each yield sending back the user input that was stored in the list, except the last one. This requires writing the generator function in a particular way (you have to avoid setting "external" state), but it works and is more flexible than automata, I think.
- jupp0r 4y agoThe problem with this is that it breaks some tooling (async stack traces in Chrome dev tools for example).
- c-baby 4y agoThis seems a lot more complicated than something like this: if (!(value instanceof Promise)) { value = Promise.resolve(value) } And then you write the rest of your code as if it is async.
- tobr 4y agoNot as if. You’re making a synchronous call async. And so it keeps spreading through your code. Op’s trick is certainly more complicated but looks like a smart way to support async cases but still letting synchronous calls stay synchronous.
- c-baby 4y agoHmm I see. So the value is in creating a kind of blocking promise. I can see how that might be useful in certain circumstances, but using blocking functionality isn't something you'd want to rely on in general.
- qazxcvbnm 4y agoNo, the promise doesn't block, it just stays a promise. However, for synchronous calls, the Promise overhead is obviated. In other words, the performance price of synchronous vs asynchronous calls is the price of a function call vs Promise implementation (i.e. event-loop machinery); the price of a function call, including the stack frame allocation, which is non-zero (recall the times that assembly programmers would dismiss languages like C as 'too-slow', having to allocate on function calls), versus pushing the Promise closure onto an event-loop, exiting the current event-loop, waiting for the next event-tick, popping off the next closure from the event stack, then creating the function call stack frame under a closure. For inner-loops, the difference can be 20ms vs 20s.
- pmoleri 4y agoI don't get it, the signature still returns `Type | Promise<Type>` how that promise doesn't spread through your code? Still, the other comment about using await on plain values seems like a better option than parent comment
- bakkoting 4y agoBabel does this, using a library called gensync. I have a writeup which goes into more detail: https://writing.bakkot.com/gensync https://writing.bakkot.com/gensync gensync instead creates two functions, one sync and one async, which is probably a more familiar API for consumers of your code.