0

I have some javascript code that I am calling in casperJS, its quite short so I have included the whole listing

var links = []; 
var casper = require('casper').create();

function getLinks() {
  var links = document.querySelectorAll('table');
  return Array.prototype.map.call(links, function(e) {
    return e.getAttribute('id');
  }); 
}

casper.start('example.html', function() {
  links = this.evaluate(getLinks);
});

casper.run(function() {
  this.echo(links.length + ' links found:');
  this.echo(' - ' + links.join('\n - ')).exit();
});

This outputs the expected

3 links found:
 - table A
 - table B
 - table C

Whereas switching to breaking out the anonymous function in getLinks so that getLinks is replaced with the below two functions

function extract(e) {
  return e.getAttribute('id');
}

function getLinks() {
  var links = document.querySelectorAll('table');
  return Array.prototype.map.call(links, extract);
}

Yields

TypeError: 'null' is not an object (evaluating 'links.length')                  
  /Users/jrrpl/git/gamecock/download.js:18
  /usr/local/Cellar/casperjs/1.1-beta3/libexec/modules/casper.js:408 in checkStep

UPDATE

It seems that the reference to the named function causes casper.run() to execute early. Anyone know why this would occur?

7
  • 10
    Those look like they are equivalent, so something else must be going on. Commented Apr 12, 2014 at 16:44
  • Please show us the code for how links is defined. Commented Apr 12, 2014 at 16:45
  • Have you tried setting a breakpoint in the anonymous function and the named extract function to compare what's going on? Inspecting some values of e in both places could be very informative. Commented Apr 12, 2014 at 16:50
  • There's also the possibility you have a variable name conflict. Commented Apr 12, 2014 at 16:52
  • querySelectorAll will never return null, nor will Array.prototype.map, so I think we're missing something. Is this a complete example, or did you change something to make it shorter? Commented Apr 12, 2014 at 17:22

1 Answer 1

2

The problem is with this.evaluate(getLinks);. The docs state:

Basically PhantomJS’ WebPage#evaluate equivalent.

Understanding evaluate()

The concept behind this method is probably the most difficult to understand when discovering CasperJS. As a reminder, think of the evaluate() method as a gate between the CasperJS environment and the one of the page you have opened; everytime you pass a closure to evaluate(), you’re entering the page and execute code as if you were using the browser console.

Even the PhantomJS docs don't state (any more? Did I miss it?) what exactly happens. The source code though is quite explicit:

page.evaluate = function (func, args) {
    var str, arg, argType, i, l;
    if (!(func instanceof Function || typeof func === 'string' || func instanceof String)) {
        throw "Wrong use of WebPage#evaluate";
    }
    str = 'function() { return (' + func.toString() + ')(';
    for (i = 1, l = arguments.length; i < l; i++) {
        …
        str += JSON.stringify(arg) + ",";
        …
    }
    str = str.replace(/,$/, '') + '); }';
    return this.evaluateJavaScript(str);
};

No we also see why it is required that all arguments to the function need to be serializable: The whole thing is converted to a code string that is then injected in the page - "executed as if it was pasted into the console".

This means that closures do not work, and you will end up with extract being undefined in the page. If you did use

function getLinks() {
    function extract(e) {
        return e.getAttribute('id');
    }
    var links = document.querySelectorAll('table');
    return Array.prototype.map.call(links, extract);
}

then it should work.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.