For your use case it is seems better to use child_process.fork() module from node.js running scripts in vm is not considered a good practice, and is very hard to ensure security.
child_process already has the callback mechanism implemented so you can easily assign handlers on finish.
In order to answer your questions I made a little script:
const vm = require('vm');
const sandbox = {
cb: (err, data) => {
if (err) console.log(err);
console.log(data);
},
};
const code = `
function test() {
cb(null, 'from vm');
}
test();
`;
process.nextTick(() => {
console.log('from event loop');
});
vm.runInNewContext(code, sandbox);
The results of the scripts are:
from vm
from event loop
Which gave me the following conclusions:
Does each 'vm' run on its own thread (or as a separate process) ?
No, vm runs synchronously. if it was running asynchronously the process next tick pushes the console.log() in front of libuv queue so we would get from event loop first and from vm second.
Is a callback mechanism supported so the script can let the calling code know it's done, etc.?
Actually it is not supported natively but you can manage to write code in the manner I did with callback in the sandbox, the single shared state between vm and your code is the global variable.