Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API to append call to arbitrary function #147

Open
jandrewb opened this issue Sep 13, 2018 · 5 comments · May be fixed by #201
Open

API to append call to arbitrary function #147

jandrewb opened this issue Sep 13, 2018 · 5 comments · May be fixed by #201

Comments

@jandrewb
Copy link

There does not appear to be a way to call an unnamed function from an external API. If the function to be called was declared ahead of time, it has a name. So the external API can append JavaScript to call it by name. It would be nice if there was a similar way to call unnamed functions.

Unnamed function example:

externalAPI(function(){
alert('external API is finished');
});

@cpcallen
Copy link
Collaborator

Works for me:

> myInterpreter.appendCode('(function(){return 42;})();');
> myInterpreter.run();
> myInterpreter.value;
=> 42

@jandrewb
Copy link
Author

I appreciate you looking at this. Let me be more specific. I'm trying to make an external API that wraps setInterval. I have code like the following in my init function:

var wrapper = function(cb, ms) {
  return setInterval(function(){interpreter.appendCode(cb.node.id.name+'();');}, ms);
};

interpreter.setProperty(scope, 'setInterval', interpreter.createNativeFunction(wrapper));

This works if the callback function already exists:

function show() {
  alert('got called');
}

setInterval(show, 1000);

But not if an unnamed function is used:

setInterval(function(){ alert('got called'); }, 1000);

So I'm asking for a way to save a reference to an unnamed function that can be called later on. Perhaps there is a better way to achieve the same result?

@cpcallen
Copy link
Collaborator

Ahh. Implementing asynchronous functions is a bit tricky.

The most straight-forward approach would be to use the technique discussed in the documentation in the paragraph beginning "Asynchronous API functions…", which will give you a synchronous wrapper for an async function. (You'll still need to make sure that someone calls .step() or .run() once the timer has fired and execution can resume.)

If you want the API to appear asynchronous from the point of view of the user code (like setTimeout usually is) then you'll need to have the native function save the callback somewhere on the Interpreter instance—and probably in an array of some kind, as you can have multiple outstanding timers—and then have your native setTimeout callback arrange to somehow wait for the interpreter stateStack to be empty and then push a new, partially-evaluated CallExpression state onto the stack. (This is where what you are requesting would make life easier.)

But if you don't mind the array of timer callbacks being user-manipulable, you could store the callback in a global pseudoArray (say, timers), then your native timeout callback can just do interpreter.appendCode('timers[' + timerID + ']();');. It's a bit kludgy, but it will work.

@cpcallen cpcallen reopened this Sep 14, 2018
@cpcallen cpcallen changed the title Call unnamed function externally - feature request API to append call to arbitrary function (e.g. for implementing setTimeout callbacks) Sep 14, 2018
@cpcallen cpcallen changed the title API to append call to arbitrary function (e.g. for implementing setTimeout callbacks) API to append call to arbitrary function Sep 14, 2018
@jandrewb
Copy link
Author

jandrewb commented Sep 14, 2018

This is exactly what I'm trying to do. I hadn't thought of saving the callback within the interpreter instance. The part that is still unclear to me is how would a native function save the callback to a pseudoArray?

A more elegant solution would be much appreciated, too.

@semiaddict
Copy link

Hi,

I know this is an old thread, but I'm also interested in calling an interpreted callback.
I am basically using blockly with a custom statement block allowing users to specify code to be executed on specific events (clicking on an element, a change in a reactive variable, etc).

After having read the various related issues here, I thought of a simpler solution which seems to work and seems close to what is done in the browser (as far as I can understand):

When a callback needs to be executed, simply append its code to the interpreter and continue the execution if it was stopped.

Here's my interpreter's simplified code:

// Whether the interpreter is stopped (did not yet run, or reached the end).
let stopped = true;

// Execute a step at a time until the end is reached.
function step(interpreter) {
  if (interpreter.step()) {
    stopped = false;
    window.setTimeout(() => {
      step(interpreter);
    }, 0);
  } else {
    stopped = true;
  }
}

// Run code
export function run(code, init) {
  const interpreter = new Interpreter(code, init);
  step(interpreter);
}

// Append code and continue execution if stopped
export function append(code, interpreter) {
  interpreter.appendCode(code);

  if (stopped) {
    step(interpreter);
  }
}

And here's an example usage:

// Keep track of listeners to be able to remove them.
const listeners = [];

// Define 'addEventListener' property.
interpreter.setProperty(
  globalObject,
  "addEventListener",
  interpreter.createNativeFunction(function (id, type, callback) {
    const el = document.getElementById(id);

    // Wrap the callback.
    const wrapper = function () {
      append(callback, interpreter);
    };

    // Add the event listener
    el.addEventListener(type, wrapper);
    
    // Add to the list of listeners
    listeners.push({
      el,
      type,
      callback: wrapper,
    });

    return true;
  })
);

Is this a bad thing to do? Can it have bad side effects?

[moved here from #153 as that one is closed]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants