vm-browserify
vm-browserify copied to clipboard
setTimeout is not working
setTimeout, setInterval and other interval built in methods do not work, even if I expose them in context.
console.log('start');
setTimeout(()=> {
console.log('hello timeout');
}, 2000);
console.log('end');
output:
start
end
It is to be noted that when I add .bind(this) , then timeout works and breaks at that line saying .bind is not a function.
console.log('start');
setTimeout(()=> {
console.log('hello timeout');
}, 2000).bind(this);
console.log('end');
output:
start
hello timeout
// and an error in console saying setTimeout(...).bind is not a function
yeah, this is because vm-browserify creates an iframe to run the code in, and immediately destroys it after running the code. so if you schedule async callbacks, they will not get called.
it would be nice to find a solution for this, i'm not sure how to do it because we don't want to keep the iframes around forever.
But why does it work when I write .bind(this), and script stops working at that line too, because of the errro .bind is not a function.
setTimeout doesn't return anything, so you can't call methods on it. setTimeout itself is synchronous, so the code following it executes immediately. This is true in both cases. The .bind call throws an error synchronously after the callback is scheduled. So in both cases a callback is scheduled but never run because the iframe is destroyed
On April 20, 2019 5:53:21 PM GMT+02:00, Ahmad Ali [email protected] wrote:
But why does it work when I write .bind(this), and script stops working at that line too, because of the errro
.bind is not a function.-- You are receiving this because you commented. Reply to this email directly or view it on GitHub: https://github.com/browserify/vm-browserify/issues/24#issuecomment-485137832
-- Sent from my Android device with K-9 Mail. Please excuse my brevity.
The issue is same for builtin vm module but I tested this in node.js and it works fine. I think there is some problem of this of the setTimeout, but I dont know how to solve it.
This issue is also discussed here, any workarounds are appreciated. setTimeout and/or context issue (runInContext) setTimeout not run in webview's Node vm
The .bind error is fixed by putting it on the function instead of on setTimeout:
setTimeout(
function () {
// ...
}.bind(this)
)
But it's just not possible to use setTimeout in vm-browserify. async code doesn't work, because we destroy the iframe immediately.
If you say that the iFrame is destroyed in which the code was running then how my custom implementation of setTimeout and setInterval is working.
const setTimeouts = [];
function customSetTimeout(cb, interval) {
const now = window.performance.now();
const index = setTimeouts.length;
setTimeouts[index] = () => {
cb();
};
setTimeouts[index].active = true;
const handleMessage = (evt) => {
if (evt.data === index) {
if (window.performance.now() - now >= interval) {
window.removeEventListener('message', handleMessage);
if (setTimeouts[index].active) {
setTimeouts[index]();
}
} else {
window.postMessage(index, '*');
}
}
};
window.addEventListener('message', handleMessage);
window.postMessage(index, '*');
return index;
}
function customClearTimeout(setTimeoutId) {
if (setTimeouts[setTimeoutId]) {
setTimeouts[setTimeoutId].active = false;
}
}
Even if I add customSetTimeout for 1 minute, it works
const codeToEval = `console.log('start)
var a = setTimeout(function() {
console.log('setTimeout async done');
}, 10000);
console.log('sync');`
const sandbox = {
setTimeout: customSetTimeout,
clearTimeout: customClearTimeout,
};
vm.runInNewContext(codeToEval, sandbox);
It's because there are multiple windows at play. If you call window.setTimeout inside the iframe, a new callback is scheduled inside the iframe. If the iframe is destroyed, this callabck will not run.
If you call your custom setTimeout, or pass the setTimeout function like so:
vm.runInNewContext('code', { setTimeout: window.setTimeout })
this calls out to the parent window; so in fact your callback is scheduled in the parent window, not inside the iframe. Then if the iframe is destroyed the callback still lingers and will be called by the parent window.
I tried checking your suggestion
vm.runInNewContext('code', { setTimeout: window.setTimeout })
but it doesn't work. Custom setTimeout do work.
I ended up creating a custom setTimeout, setInterval, clearTimeout, clearInterval, explained in the link below. And it plays very well! https://gist.github.com/ahmadalibaloch/6c7d70244c83b90aa77bb83fa28cd0df
I've found a faster solution:
vm.runInNewContext('code', {
setTimeout: (callback, wait) => setTimeout(() => callback(), wait),
clearTimeout: handle => clearTimeout(handle)
})