I am currently trying to implement following code with Zombie.js. Yet, I am unable to make the following code to work:
var Browser = require('zombie');
browser = new Browser();
browser.wait(3000, function() { console.log("ok"); });
So, the script should wait 3 seconds before displaying "ok". Yet, it displays it immediately.
Am I misunderstanding something?
Thanks for your help!
As the documentation states:
Waits for the browser to complete loading resources and processing
JavaScript events.
Since you're not requesting anything, there's nothing to wait for, so Zombie calls the callback immediately. It's more of a maximum timeout kind of thing, not a guaranteed wait.
Related
Our Chrome extension has both content and background scripts communicating with each other. When the plugin is updated, the background script is stopped and the content scripts start getting Error: Extension context invalidated.. In V2, we used port.onDisconnect event as described here to clean things up. But in V3, this event is also sent after 5 minutes (when the background service worker is automatically terminated). So this event now means either extension unloading (and the cleanup should be done), or just SW lifecycle event (no need to cleanup, reconnecting is fine).
So the question is, how to unambiguously determine whether the cleanup is necessary.
I've tried:
chrome.management. events: onDisabled etc. But unfortunately chrome.management is undefined in my content script.
Checking for chrome.runtime.id inside port.onDisconnected callback to determine the plugin is unloaded. But the id is still present at that moment.
Again inside port.onDisconnected, trying to do chrome.runtime.connect() again and catching the exception. But there's no exception! The port is created successfully, but it receives neither messages nor its own onDisconnected events.
Trying point 3 inside setTimeout(..., 0) and setTimeout(..., 100). The former doesn't produce exceptions either. The latter does, but it introduces a delay of questionable duration (why 100? would it work the CPU is overloaded?) and potential race conditions when other plugin functionality could try to send messages with unpredictable results. So I'd appreciate a more bullet-proof solution.
Thanks to wOxxOm's suggestions, I've found a solution that seems to work for now: every once in a while (<5 seconds) to disconnect the port in the content script and then reconnect again. The code looks like this:
let portToBackground: chrome.runtime.Port | undefined = openPortToBackground();
function openPortToBackground(): chrome.runtime.Port {
const port = chrome.runtime.connect();
const timeout = setTimeout(() => {
console.log('reconnecting');
portToBackground = openPortToBackground();
port.disconnect();
}, 2 * 60 * 1000); // 2 minutes here, just to be sure
port.onDisconnect.addListener(() => {
clearTimeout(timeout);
if (port !== portToBackground) return;
// perform the cleanup
});
return port;
}
export function isExtensionContextInvalidated(): boolean {
return !portToBackground;
}
I am new to node.js and working through the API. In the stream module docs I came across this example of the "unpipe event" (actually a fusion of two examples in the docs).
const fs = require("fs);
const writable = fs.createWriteStream("write.txt");
const readable = fs.createReadStream("read.txt");
readable.pipe(writable);
setTimeout(function(){
console.log("Stop writing to file.txt");
readable.unpipe(writable);
console.log("Manually close the file stream");
writable.end();
}, 0);
writable.on("unpipe", function(src){
console.log("Something has stopped piping into the writer");
});
I can't understand the following console.log order:
"Stop writing to file.txt"
"Something has stopped piping into the writer"
"Manually close the file stream"
Given the setTimeout callback is running - which is the first phase of the event loop as I understand - how on earth does the callback for the "unpipe" event start to run before the setTimeout callback has finished.
Originally I had the setTimeout firing after a time above zero seconds, however I was finding that the unpipe call back was always called first. I reasoned that my computer was reading the file always first before the setTimeout was ready. (Although I can't see any mention in the docs about the completion of the write to the file eliciting the "unpipe" event, but this makes sense I suppose). However I can't for the life of me reason how the above program flow is occurring. Thanks in advance for any help.
As specified by the node.js documentation:
The EventEmitter calls all listeners synchronously in the order in which they were registered.
That is, when .emit is called, it synchronously runs through all listeners for the emitted event and calls them.
Note that if necessary you can wrap your callback code in process.nextTick to ensure that it will always run asynchronously, but in your case it's likely that's unnecessary.
Also the source of the call to .emit (the emission of the event) will often be asynchronous.
EDIT
I have noticed the removal of the .end() function appears to solve the issue, but after reading the Nightmare docs on the use of .end() it says: Completes any queue operations, disconnect and close the electron process.
Now while this does solve the problem, am I now just opening more and more electron processes each time the route is called, which will eventually cause the server to run out of memory, or is this a safe way to fix the issue?
ORIGINAL TEXT
Please consider the following problem:
I am developing a Node based service that will allow the user to request screenshot of a particular URL.
For this I am using Nightmare to visit the URL, wait 2 seconds, take a screenshot, which is saved to the disk, convert it to base64, delete the image and then return the base64 string.
console.log('Nightmare starts');
nightmare
.goto(url)
.wait(2000)
.screenshot(filename)
.end()
.then(function (result)
{
fs.exists(filename, function(exists)
{
if (exists)
{
data = fs.readFileSync(filename);
var base64 = data.toString('base64')
fs.unlink(filename);
var output = {'message':'success','map_image':base64};
res.send(output);
}
});
})
.catch(function (error)
{
console.error('Search failed:', error);
});
console.log("Nightmare Finished");
The above code works just fine, the first time it runs. However any subsequent calls to this just consoles "Nightmare starts" and "Nightmare Finished" instantly with the actual code in-between not running. I don't appear to have any errors display, nothing is caught if I wrap it in a try/catch. The node requires a reboot to allow it to happen again.
Something worth noting is that I am running on a headless ubuntu machine, as electron (one of the nightmare dependencies) appears to need a GUI, I am using xvfb to launch the node using the following command:
xvfb-run --auto-servernum --server-num=1 node server.js
I'm assuming this may be an issue with some resource not being released correctly on the first run, but any assistance would be appreciated.
Also open to any constructive criticism of my code, very new to Node and i'm sure i'm not writing in the most optimal way (sync file loading etc)
It appears that you are simply misplacing where you are creating the nightmare instances. Cannot help much without some more code snippet and information.
Way 1
Create nightmare instance every time and close them after you are done with your task. It will require some time to boot up the instance, but it will also lessen the memory load. Not to mention you can have multiple nightmare instances for different users.
Way 2
Don't end and re-use same nightmare instance. Have multiple nightmare instances and queue the call for screenshot. The websites will load fast and it won't take time to boot up an instance, but you will have longer wait time for longer queue.
I want to build a extension that behaves like a timer. It should count down the seconds when activated, but should do nothing with inactive.
The chrome.alarms API is interesting, but does not have enough precision nor granularity. It only fires at most once per minute, and it may fire late. If I want something to execute more often than that, I can't use this API.
Then, the next natural solution is to use a background page and use setTimeout or setInterval in there. However, background pages are persistent, and they take up resources (e.g. memory) even when idle. So they are not ideal.
The best solution seems to be an event page to run the timer. However, the documentation says:
Once it has been loaded, the event page will stay running as long as it is active (for example, calling an extension API or issuing a network request).
[…]
Once the event page has been idle a short time (a few seconds), the runtime.onSuspend event is dispatched. The event page has a few more seconds to handle this event before it is forcibly unloaded.
[…]
If your extension uses window.setTimeout() or window.setInterval(), switch to using the alarms API instead. DOM-based timers won't be honored if the event page shuts down.
Unfortunately, having an active setInterval is not enough to consider an event page active. In fact, from my tests, an interval up to 10 seconds is short enough to keep the event page running, but anything greater than 10 or 15 seconds is too far apart and the event page will get unloaded. I've tested this on my crx-reload-tab project.
I believe what I want is a middle ground:
I want a background page that I can load and unload on demand. (Instead of one that keeps loaded all the time.)
I want an event page that stays persistent in memory for as long as I say; but otherwise could be unloaded. (Instead of one that gets unloaded automatically by the browser.)
Is it possible? How can I do it?
Background pages cannot be unloaded on demand, and Chrome decides Event page lifecycle for you (there is nothing you can do in onSuspend to prevent it).
If your concern is timers, you could try my solution from this answer, which basically splits a timer into shorter timers for a "sparse" busy-wait. That's enough to keep the event page loaded and is a viable solution if you don't need to do that frequently.
In general, there are some things that will keep an event page loaded:
If you're using message passing, be sure to close unused message ports. The event page will not shut down until all message ports are closed.
This can be exploited if you have any other context to keep an open Port to, for example a content script. See Long-lived connections docs for more details.
In practice, if you often or constantly need precise, sub-minute timers, an Event page is a bad solution. Your resource gains from using one might not justify it.
As mentioned in Xan's answer we can abuse messaging. There's nothing wrong about it either in case you want to temporarily prevent the event page from unloading. For example while displaying a progress meter using chrome.notifications API or any other activity based on setTimeout/setInterval that may exceed the default unload timeout which is 5-15 seconds.
Demo
It creates an iframe in the background page and the iframe connects to the background page. In addition to manifest.json and a background script you'll need to make two additional files bg-iframe.html and bg-iframe.js with the code specified below.
manifest.json excerpt:
"background": {
"scripts": ["bg.js"],
"persistent": false
}
bg.js:
function preventUnload() {
let iframe = document.querySelector('iframe');
if (!iframe) {
iframe = document.createElement('iframe');
document.body.appendChild(iframe).src = 'bg-iframe.html';
}
}
function allowUnload() {
let iframe = document.querySelector('iframe');
if (iframe) iframe.remove();
}
chrome.runtime.onConnect.addListener(() => {});
bg-iframe.html:
<script src="bg-iframe.js"></script>
bg-iframe.js:
chrome.runtime.connect();
Usage example in bg.js:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === 'start') doSomething();
});
function doSomething() {
preventUnload();
// do something asynchronous that's spread over time
// like for example consecutive setTimeout or setInterval calls
let ticks = 20;
const interval = setInterval(tick, 1000);
function tick() {
// do something
// ................
if (--ticks <= 0) done();
}
function done() {
clearInterval(interval);
allowUnload();
}
}
I use this function:
function _doNotSleep() {
if (isActive) {
setTimeout(() => {
fetch(chrome.runtime.getURL('manifest.json'));
_doNotSleep();
}, 2000);
}
}
But the problem with such approach is that Devtools network tab polluted with this http stub.
Consider:
node -e "setTimeout(function() {console.log('abc'); }, 2000);"
This will actually wait for the timeout to fire before the program exits.
I am basically wondering if this means that node is intended to wait for all timeouts to complete before quitting.
Here is my situation. My client has a node.js server he's gonna run from Windows with a Shortcut icon. If the node app encounters an exceptional condition, it will typically instantly exit, not leaving enough time to see in the console what the error was, and this is bad.
My approach is to wrap the entire program with a try catch, so now it looks like this: try { (function () { ... })(); } catch (e) { console.log("EXCEPTION CAUGHT:", e); }, but of course this will also cause the program to immediately exit.
So at this point I want to leave about 10 seconds for the user to take a peek or screenshot of the exception before it quits.
I figure I should just use blocking sleep() through the npm module, but I discovered in testing that setting a timeout also seems to work. (i.e. why bother with a module if something builtin works?) I guess the significance of this isn't big, but I'm just curious about whether it is specified somewhere that node will actually wait for all timeouts to complete before quitting, so that I can feel safe doing this.
In general, node will wait for all timeouts to fire before quitting normally. Calling process.exit() will exit before the timeouts.
The details are part of libuv, but the documentation makes a vague comment about it:
http://nodejs.org/api/all.html#all_ref
you can call ref() to explicitly request the timer hold the program open
Putting all of the facts together, setTimeout by default is designed to hold the event loop open (so if that's the only thing pending, the program will wait). You can programmatically disable or re-enable the behavior.
Late answer, but a definite yes - Nodejs will wait around for setTimeout to finish - see this documentation. Coincidentally, there is also a way to not wait around for setTimeout, and that is by calling unref on the object returned from setTimeout or setInterval.
To summarize: if you want Nodejs to wait until the timeout has been called, there's nothing you need to do. If you want Nodejs to not wait for a particular timeout, call unref on it.
If node didn't wait for all setTimeout or setInterval calls to complete, you wouldn't be able to use them in simple scripts.
Once you tell node to listen for an event, as with the setTimeout or some async I/O call, the event loop will loop until it is told to exit.
Rather than wrap everything in a try/catch you can bind an event listener to process just as the example in the docs:
process.on('uncaughtException', function(err) {
console.log('Caught exception: ' + err);
});
setTimeout(function() {
console.log('This will still run.');
}, 500);
// Intentionally cause an exception, but don't catch it.
nonexistentFunc();
console.log('This will not run.');
In the uncaughtException event, you can then add a setTimeout to exit after 10 seconds:
process.on('uncaughtException', function(err) {
console.log('Caught exception: ' + err);
setTimeout(function(){ process.exit(1); }, 10000);
});
If this exception is something you can recover from, you may want to look at domains: http://nodejs.org/api/domain.html
edit:
There may actually be another issue at hand: your client application doesn't do enough (or any?) logging. You can use log4js-node to write to a temp file or some application-specific location.
Easy way Solution:
Make a batch (.bat) file that starts nodejs
make a shortcut out of it
Why this is best. This way you client would run nodejs in command line. And even if nodejs program returns nothing would happen to command line.
Making bat file:
Make a text file
put START cmd.exe /k "node abc.js"
Save it
Rename It to abc.bat
make a shortcut or whatever.
Opening it will Open CommandLine and run nodejs file.
using settimeout for this is a bad idea.
The odd ones out are when you call process.exit() or there's an uncaught exception, as pointed out by Jim Schubert. Other than that, node will wait for the timeout to complete.
Node does remember timers, but only if it can keep track of them. At least that is my experience.
If you use setTimeout in an arrow / anonymous function I would recommend to keep track of your timers in an array, like:
=> {
timers.push(setTimeout(doThisLater, 2000));
}
and make sure let timers = []; isn't set in a method that will vanish, so i.e. globally.