This script demonstrates challenges when inlining async function calls like setTimeout
as well as a solution.
inspect-code
does this.
Things to be solved:
setTimeout
needs to return a timer id.clearTimeout
async
/await
fn(...args)
This script demonstrates challenges when inlining async function calls like setTimeout
as well as a solution.
inspect-code
does this.
Things to be solved:
setTimeout
needs to return a timer id.clearTimeout
async
/await
fn(...args)
let x = 1 | |
// This mutation of `x` is supposed to happen immeadiately. | |
const mutateX = (a) => {x = a} | |
mutateX(2) // `x` is now `2`. | |
// This mutation of `x` is supposed to happen after all sync code has run. | |
const obj = {mutateX: () => {x++}} | |
const defer = setTimeout | |
defer(obj.mutateX, 10) | |
defer(obj.mutateX, 5) | |
assert(x === 2) |
// The async code needs to run after all sync code, therefore at the end of the script. | |
// The async fn calls might be deeply nested inside function scopes. | |
// We need to declare global "anchor" variables to access them at the end of the script. | |
let _ref1, _ref2, _ref3, _ref4, _ref5 | |
let x = 1 | |
const mutateX = (a) => {x = a} | |
_ref1 = mutateX // global "anchor" variable | |
// `_now` will skip on async functions. Sync functions will just be executed. | |
_now(_ref1, 2) | |
const obj = {mutateX: () => {x++}} | |
const defer = setTimeout | |
// As `obj` might have a getter fn for `mutateX`, we need to chache the value in a variable. | |
// Doing that, we will only access it as often as in the original code. | |
_ref2 = defer | |
_ref3 = obj.mutateX | |
_now(_ref2, _ref3, 10) | |
// second call | |
_ref4 = defer | |
_ref5 = obj.mutateX | |
_now(_ref4, _ref5, 5) | |
assert(x === 2) | |
// `_later` will skip on non-async functions. Callbacks of `setTimeout` will be invoked immediately. | |
// `setTimeout` might be in the "wrong" order. `_later` needs to sort them by delay. | |
_later([ | |
{fn: _ref1, args: [2]}, | |
{fn: _ref2, args: [_ref3, 10]}, | |
{fn: _ref4, args: [_ref5, 5]} | |
]) | |
assert(x === 3) |
const _now = (fn, ...args) => { | |
if (fn === setTimeout) return | |
return fn(...args) | |
} | |
const _later = (calls) => { | |
calls | |
.filter((call) => call.fn === setTimeout) | |
.sort((call1, call2) => call1.args[1] - call2.args[1]) // sort by delay | |
.forEach(({args}) => args[0]()) // call callback synchronously | |
} |
const x = {y: setTimeout}
x['zy'[1]](fn, 1) // This aliasing process can get arbitrarily complex.
The child process could run the code inside a vm
. The parent process would collect values from the child until the child process closes.
However, the client would have to serialize the values before sending them to the client. There's JS expressions that can't be serialized correctly.