The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use one webpage object and start the next page.open() from the previous call’s completion callback. Check the callback’s status before processing the page, finish all page-specific work, and call phantom.exit() only after the final URL has been handled. Starting several navigations immediately on the same object creates overlapping loads and nondeterministic results.
The reliable pattern: a callback-driven URL queue
PhantomJS navigation is asynchronous. The optional callback passed to page.open runs when loading completes and receives either success or fail. That makes the callback the hand-off point between pages.
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
The Phantom of the Opera (Full Screen Edition) | $15.38 | Buy on Amazon |
| 2 |
|
The Phantom of the Opera at the Royal Albert Hall | $8.99 | Buy on Amazon |
| 3 |
|
The Phantom of the Opera (Two-Disc Special Edition) | $12.99 | Buy on Amazon |
| 4 |
|
Phantom of the Opera | $9.99 | Buy on Amazon |
| 5 |
|
The Phantom of the Opera (2004) | Buy on Amazon |
The following script opens three pages in order, processes each only after its load finishes, and exits after the queue is empty:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
var webpage = require('webpage');
var page = webpage.create();
var urls = [
'https://example.com/one',
'https://example.com/two',
'https://example.com/three'
];
var index = 0;
function openNext() {
if (index >= urls.length) {
phantom.exit();
return;
}
var url = urls[index++];
page.open(url, function (status) {
if (status === 'success') {
console.log('Loaded: ' + url);
// Read, inspect, or render this page here.
var title = page.evaluate(function () {
return document.title;
});
console.log('Title: ' + title);
} else {
console.log('Failed to load: ' + url);
// Choose whether to continue, retry, or stop.
}
// Start the next navigation only after this callback is complete.
openNext();
});
}
openNext();
Save it as multi.js and run it with the PhantomJS executable installed on your system:
#1 Best Overall
- This Certified Refurbished product is tested and certified to look and work like new. The refurbishing process includes functionality testing, basic cleaning, inspection, and repackaging. The product ships with all relevant accessories, a minimum 90-day warranty, and may arrive in a generic box.
phantomjs multi.js
The important detail is not the recursive function name. It is the sequencing: openNext() is called from inside the callback, after the current page’s inspection or rendering has finished. The same page object is therefore never asked to navigate twice at once.
Why the status check matters
A callback can report fail for a network error, an unreachable host, or another load problem. Do not assume that a failed navigation produced a usable document. Keep logging the URL alongside the status so a long run can be diagnosed. You can continue to the next item, retry a transient failure, or stop the run when a particular URL is essential.
Why an unconditional exit is wrong
This starts an asynchronous operation and then terminates the process before its callback can run:
Recommended Free Tools
page.open('https://example.com', function (status) {
console.log(status);
});
phantom.exit(); // Too early
Place phantom.exit() in the queue’s completion branch, or in an equivalent “all work finished” path. Otherwise PhantomJS may end before later pages are opened, or it may remain alive after your work is complete.
Rank #2
Doing useful work for every page
Put page-specific logic in the successful branch of the callback. For example, this version writes a screenshot for each URL and gives files stable names:
var webpage = require('webpage');
var page = webpage.create();
var urls = [
'https://example.com/one',
'https://example.com/two'
];
var index = 0;
page.viewportSize = { width: 1440, height: 900 };
function openNext() {
if (index >= urls.length) {
phantom.exit();
return;
}
var number = index;
var url = urls[index++];
page.open(url, function (status) {
if (status === 'success') {
page.render('page-' + number + '.png');
console.log('Rendered ' + url);
} else {
console.log('Could not render ' + url);
}
openNext();
});
}
openNext();
Keep all processing that depends on the loaded page before the call to openNext(). If you need to write data asynchronously outside PhantomJS’s normal page APIs, add your own completion signal and advance only after that work is done.
Reading the DOM with page.evaluate
page.evaluate executes in the loaded page’s context, so it can access document and the DOM. Values crossing back to the outer script must be simple serializable values. Functions, closures, and DOM nodes cannot be passed directly between the two contexts.
var result = page.evaluate(function () {
var links = document.querySelectorAll('a');
var values = [];
for (var i = 0; i < links.length; i++) {
values.push({
text: links[i].textContent,
href: links[i].href
});
}
return {
title: document.title,
links: values
};
});
console.log(JSON.stringify(result));
Keep the URL list, counters, retry policy, file paths, and the phantom object in the outer script. Use evaluate only for page-side operations and return strings, numbers, booleans, arrays, or plain objects composed of those values.
Rank #3
- DVD
- AC-3, Closed-captioned, Color
- English (Subtitled), Spanish (Subtitled), French (Subtitled)
- 2
- 141
Handling failures, retries, and an incomplete run
Continue after a failed URL
The basic queue already continues after fail. This is appropriate for batch collection where one unavailable site should not block all other URLs. Record the failure in a report or write it to standard output.
Retry a URL a limited number of times
Retries must be bounded. Otherwise a permanently broken URL can prevent phantom.exit() forever.
var webpage = require('webpage');
var page = webpage.create();
var urls = [
'https://example.com/one',
'https://example.com/two'
];
var index = 0;
var attempts = 0;
var maxAttempts = 2;
function openNext() {
if (index >= urls.length) {
phantom.exit();
return;
}
var url = urls[index];
page.open(url, function (status) {
if (status === 'success') {
console.log('Loaded: ' + url);
index++;
attempts = 0;
openNext();
return;
}
attempts++;
console.log('Failed (' + attempts + '): ' + url);
if (attempts < maxAttempts) {
openNext();
} else {
index++;
attempts = 0;
openNext();
}
});
}
openNext();
This example retries the current item once after the first failure, then records it as skipped and advances. Adjust the policy for your workload; the callback status alone does not tell you whether another attempt will succeed.
Stop on the first fatal failure
If every page is required, call phantom.exit(1) (or your chosen failure path) from the failed branch after logging the URL. Do not silently render an error page as if it were the requested document.
Rank #4
- Format: Closed-captioned, Color, Dolby, NTSC, Subtitled, Widescreen
- Language: English (Dolby Digital 5.1), French (Dolby Digital 5.1)
- Subtitles: English, French, Spanish
- Region 1 (U.S. and Canada only); Number of discs: 1
- Rated: PG-13; Run Time: 141 minutes
One page object or several?
Reuse one object for ordered work
One object is the clearest choice when order matters, when later decisions depend on earlier output, or when all pages can share the same viewport and handlers. It prevents overlapping navigation on one object and makes the exit condition straightforward. Remember that navigation replaces the current page state, so extract or save anything you need before opening the next URL.
Create separate objects for independent state
Create an object with require('webpage').create() for each independent workflow when pages need different cookies, viewport settings, event handlers, or simultaneous activity. A sketch looks like this:
var webpage = require('webpage');
var pending = 2;
function finished() {
pending--;
if (pending === 0) {
phantom.exit();
}
}
function load(url) {
var page = webpage.create();
page.open(url, function (status) {
console.log(url + ': ' + status);
if (status === 'success') {
// Process this page before releasing it.
console.log(page.evaluate(function () { return document.title; }));
}
finished();
});
}
load('https://example.com/one');
load('https://example.com/two');
This enables concurrent loads, but the available PhantomJS documentation does not define a universal safe concurrency limit or promise a particular memory cost. Test the number of simultaneous pages in the version and environment you actually deploy. Track every callback and exit only when the full count reaches zero.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the first URL opens | The next page.open is never called, or it is called outside the callback. |
Call your queue function at the end of every callback branch. |
| Pages appear mixed or a screenshot is from the wrong URL | The same page object received overlapping navigations. | Serialize navigation with the callback, or use separate page objects. |
| The process exits immediately | phantom.exit() runs directly after starting an asynchronous open. |
Move exit to the “queue empty” or “all callbacks finished” path. |
| The script never terminates | There is no final exit, or a retry loop has no bound. | Call phantom.exit() after the last callback and cap retries. |
status is fail |
The URL did not load successfully. | Log the URL, skip or retry it, and do not inspect or render it as a valid page. |
| DOM data is missing | The code ran outside page.evaluate, or a non-serializable value was returned. |
Perform DOM access inside evaluate and return plain serializable data. |
| A later page has the wrong cookies or handlers | State from a reused page object is shared. | Reset what is reusable, or create a separate webpage object for that workflow. |
Operational guidance for long URL lists
- Log an index, URL, status, and elapsed time for every callback.
- Write output using deterministic names derived from the queue index rather than a URL’s punctuation.
- Keep retries finite and distinguish skipped URLs from successfully processed ones.
- Do not assume a successful load means that a particular application element is ready; if your page depends on client-side rendering, use an appropriate readiness strategy available in your PhantomJS version and validate the resulting DOM.
- Verify scripts against the PhantomJS version installed in production. PhantomJS documentation and examples are historical, and behavior can differ from the environment in which an old script was written.
Or skip the browser setup
If your goal is dependable screenshots rather than maintaining PhantomJS navigation code, ScreenshotNeo provides a website screenshot API and MCP server. One request captures a URL as PNG, JPEG, WebP, or PDF, while its capture flow accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Each response identifies whether it was a clean page, a bot check, blank page, timeout, failed load, or cache hit; only clean shots are billed.
For a single capture, see the ScreenshotNeo API documentation:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Equivalent Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also offers full-page and element captures, device presets and custom viewports, retina scale, dark mode, PDFs with paper and page-range controls, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get started.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteFrequently Asked Questions
Can I use one PhantomJS page object for unrelated domains?
Yes, provided each navigation completes and you finish extracting or saving the current page before opening the next. Use separate objects when independent cookies, handlers, or viewport state must coexist.
Does a successful page.open guarantee that JavaScript-rendered content is ready?
No. It reports the navigation outcome. Your script still needs to verify the application-specific DOM state before collecting data or rendering.
How do I know whether to serialize or parallelize a batch?
Serialize when ordering and simple control are more important than throughput. Use multiple webpage objects only for genuinely independent work, and establish a concurrency level by testing your deployed PhantomJS version and host.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

