Skip to content
This repository has been archived by the owner on May 30, 2023. It is now read-only.

setTimeout ignores timeout when called from page.includeJs() callback #10832

Closed
ariya opened this issue Oct 20, 2012 · 49 comments
Closed

setTimeout ignores timeout when called from page.includeJs() callback #10832

ariya opened this issue Oct 20, 2012 · 49 comments
Labels

Comments

@ariya
Copy link
Owner

ariya commented Oct 20, 2012

chri...@parastudios.de commented:

Which version of PhantomJS are you using? Tip: run 'phantomjs --version'. => 1.7.0 win7 x64

What steps will reproduce the problem?
Run the attached JS file with phantomjs.
It will try and load google.com, include jQuery - both work fine and then tries do perform a setTimeout of 5 seconds. The function passed to setTimeout is called immediately.

What is the expected output? What do you see instead?
After the console log "BeforeTimeout" I expect 5 seconds to pass before "AfterTimeout" is written to the console. Instead, "AfterTimeout" is written instantly to the console.

Which operating system are you using?
Win7 x64

Did you use binary PhantomJS or did you compile it from source?
Binary

Please provide any additional information below.

Disclaimer:
This issue was migrated on 2013-03-15 from the project's former issue tracker on Google Code, Issue #832.
🌟   6 people had starred this issue at the time of migration.

@ariya
Copy link
Owner Author

ariya commented Jan 8, 2013

vasofton...@googlemail.com commented:

I also have the same problem. PhantomJS ignores setTimeout in main context (not in page.evaluate).

@timecode
Copy link

timecode commented Feb 9, 2013

rob.playford@gmail.com commented:

Yes, seen with PhantomJS 1.8.1
setTimeout ignored in main context but not in page.evaluate

@ariya
Copy link
Owner Author

ariya commented Mar 3, 2013

ByteGems...@gmail.com commented:

Doesn't work on W7x64: v1.8.1

@DGuidi
Copy link

DGuidi commented Mar 13, 2013

diegogu...@gmail.com commented:

same for me: phantomjs-1.8.2 W7x64

@javascriptlove
Copy link

i still have this in phantomjs 1.9 with ubuntu x64
any way we can progress this? need any help/tests ?

@dotcink
Copy link

dotcink commented Apr 9, 2013

Still not fixed in phantomjs 1.9.0 with Archlinux(Linux 3.8.5-1).

@JamesMGreene
Copy link
Collaborator

This does work for me on Windows 7 (64-bit) with PhantomJS 1.9.0 (downloaded binary).

Is it only a bug on Linux OSes now? Haven't seen any MacOSX users chime in either.

@dotcink
Copy link

dotcink commented Apr 11, 2013

I just tested with 1000 setTimeout()'s in includeJs()'s callback environment, and about 80% failed (the timeout value seems to become 0).
BTW, In main context all succeed.

PhantomJS 1.9.0 in ArchLinux.

@javascriptlove
Copy link

I think there are some very very very rare occasions when setTimeout doesn't work in main context. I've just tried different combinations of page.open callbacks and some nested function closures, and it works in Ubuntu with PhantomJS 1.9! But... here is a script that fails

So perhaps it has to do something with the closures or callbacks or something else

var wp = require('webpage');
var page = wp.create();

page.viewportSize = { width: 1024, height: 768 };

page.onError = function (msg, trace) {
    //console.log(msg);
    //trace.forEach(function(item) {
        //console.log('  ', item.file, ':', item.line);
    //})
}

page.customHeaders = {
    'Accept': 'text/html,application/xhtml+xml,application/rss+xml,application/xml;q=0.9,*/*;q=0.8'
};

page.settings.userAgent = 'Googlebot-News';
var step = -1;
var output = {};

var data = function (s, e) {
    e = e || document;
    return e.querySelector(s).attributes.data.value;
};

var value = function (s, e) {
    e = e || document;
    return e.querySelector(s).nodeValue;
};

var steps = [
    function() {
        output = [123];
        nextstep();
    },
    function() {
        var loadNews = function(a) {
            //page.close();
            //page = null;
            //page = wp.create();
            console.log(a, output.length);

            if (a >= output.length)
                nextstep();
            else {
                page.open("http://google.com", function(status) {
                    (function(a) {
                        console.log('Before timeout', a);
                        setTImeout(function() {
                            console.log('ok'); // -- this never gets displayed!
                            //loadNews(a);
                        }, 100); // give a breeth
                    })(a+1);
                    console.log('After timeout', a);

                });
            }
        };
        loadNews(0);
    }
];

var nextstep = function() {
    if (step < steps.length-1) {
        step++;
        steps[step]();
    } else {
        console.log(JSON.stringify(output));
        phantom.exit();
    }
};

// run step by step
nextstep();

@JamesMGreene
Copy link
Collaborator

@jahggler: You have an important typo: setTImeout instead of setTimeout.

@javascriptlove
Copy link

omg thanks, i thought it should display undefined or something, thanks a lot!
then everything regarding setTimeout in main context works

@JamesMGreene
Copy link
Collaborator

@dotcink: Did you use a different repro script than the OP did?

@dotcink
Copy link

dotcink commented Apr 12, 2013

@JamesMGreene
I tested with the binary from https://phantomjs.googlecode.com/files/phantomjs-1.9.0-linux-i686.tar.bz2 .
I've also tryied compiling https://phantomjs.googlecode.com/files/phantomjs-1.9.0-source.zip , with the result not changed .

Here is my script test.js:

var page = require('webpage').create();

function test(from) {
    for (var n = 0; n < 1000; n++) {
        console.log(new Date() + " BeforeTimeout in " + from);
        setTimeout(function() {
            console.log(new Date() + " AfterTimeout in " + from);
        }, 10000);
    }
}
page.open('http://google.com', function(){
    page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js', function(){
        test("includeJs's callback");
    });
    test("main context");
});

Then output to test.log:

$ phantomjs test.js | tee test.log
$ wc -l test.log
4000 test.log

which shows that all 4000 console.log's works.

$awk '{ print $5,$8 ; }' test.log | uniq 
20:20:34 BeforeTimeout
20:20:35 BeforeTimeout
20:20:35 AfterTimeout
20:20:44 AfterTimeout

shows that setTimeout's started at about 20:20:34 or 20:20:35, and worked at 20:20:35 or 20:20:44. (These time depend on when you run the test.)

$ grep -c "20:20:4.*AfterTimeout in main context" test.log 
1000

shows that all 1000 console.log's in setTimeout's of main context works as expected.

$ grep -c "20:20:4.*AfterTimeout in includeJs's callback" test.log

192

shows that only 192 (about 20%) console.log's in setTimeout's of includeJs's callback works well. (The result may vary a bit)

@curtisturner
Copy link

setInterval produces a similar result. It runs 1 time without waiting and doesn't run again.

I'm on XP using 1.9

Example

var page = require('webpage').create(),
    pageLoadDelay = 700,
    loadInProgress = false; 

page.onConsoleMessage = function(msg) {
    console.log(msg);
};

page.onLoadStarted = function() {
    loadInProgress = true;
};
page.onLoadFinished = function() {
    console.log("Page Loaded")
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js", function(){

        console.log("Javascript Included");

        var count = 0;
        setInterval(function() {
            if(count <= 20)
                console.log("Interval" + count++)
            else
                phantom.exit();
        }, pageLoadDelay);
    });
    loadInProgress = false;
};

page.open("google.com");

@javascriptlove
Copy link

@curtisturner, your example works fine for me on ubuntu x64 with v1.9 (binary)

@CarlQLange
Copy link

We've noticed this happen outside of includeJS, maybe one in every ten attempts. I can't share code unfortunately, but this is definitely still a pretty crazy issue.

@astjohn
Copy link

astjohn commented Aug 20, 2013

I have also experienced the issues outlined above using version 1.9.1 in both setTimeout and setInterval - the latter of which kills the nice waitFor function outlined in some of the examples. Pretty frustrating. I can't share all of the code, but it looks something like the following:

// waitFor and others defined up here..

page.open('http://www.website.com', function () {
  page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {

    // attempt to submit a form
    pageloaded = false; // global variable which is changed in onLoadFinished callback
    page.evaluate(function (text, value) {
      // form submission stuff
    }, txt, val);

    // waitFor (setInterval) and basic setTimeout don't work here.

  });

});

@davedx
Copy link

davedx commented Sep 11, 2013

Confirmed in 1.9.2 on Win 7, in main context.

@badriub
Copy link

badriub commented Sep 25, 2013

setTimeout & setTimeInterval getting ignored in win 7/64 with phantom js 1.9.2. Is there a fix available for this? Any chance the fix is missing in the windows version?

@uberbrady
Copy link

Nope, I have the same problem on a Mac.

@JamesMGreene
Copy link
Collaborator

Thus why the issue is still open.

@amitpatelx
Copy link

We are also facing same issue with phantomjs 1.9.1 on Ubuntu 12.04 32 bit

@emergedennis
Copy link

In case anyone else is struggling with this, I found that wrapping a setTimeout in another setTimeout works:

Example:

        setTimeout(function(){
            setTimeout(function(){
                // Get image data
                page.clipRect = page.evaluate(function() {
                    return document.querySelector("#Viewport").getBoundingClientRect();
                });
                // Sends back base64 image for direct display
                var imagedata = page.renderBase64('png');
                callback(imagedata);
                page.close();
            },700);
        },1);

@katiejots
Copy link

I can confirm the issue in Phantom.js 1.9.2 on Fedora. It looks like setTimeout is ignored both in the main context and page.evaluate calls.

SergioCrisostomo added a commit to SergioCrisostomo/mootools-core that referenced this issue Feb 21, 2014
Trying a patch suggested at the phantomjs github repo issues

ariya/phantomjs#10832
@SergioCrisostomo
Copy link

Any news about this? Anyone with time to check this out? just tested @emergedennis 's patch and didn't work either...

@alvarolm
Copy link

alvarolm commented Mar 4, 2014

a year later and, still not working on the main context and page evaluate (centos 6.4)

@tom76kimo
Copy link

I checked @emergedennis 's patch and it worked.
So did

setTimeout(function(){
    setInterval(function () {
        //do something...
    }, 1000);
}, 1);

I use PhantomJS 1.9.7 for windows on windows8

@PavelPolyakov
Copy link

I had the script for the phantomjs which works perfectly without any timeout workarounds on my mac:

// checking if the result is shown on the page
                (function() { 
                    var dfd = new $.Deferred(); 

                    // how many times to check before we go further
                    var checkNumber = 5;

                    var checkInterval = setInterval(function() {
                        checkNumber--;

                        var resultSuccess = page.evaluate(function() { return $('.class').is('*'); });
                        var resultEmpty = page.evaluate(function() { return $('div.class:contains(text to check)').is('*'); });

                        if (resultSuccess || resultEmpty) {
                            clearInterval(checkInterval);
                            dfd.resolve();
                        } else if(checkNumber == 0){
                            clearInterval(checkInterval);
                            dfd.reject();
                        }
                    }, 5000);

                    return dfd; 
                    })().then(/*success*/ function(){
                            var name = page.evaluate(function(){
                                return $('.class').text();
                            });

                            page.render('results_page.png');

                            var endTime = (new Date).getTime();
                            console.log(JSON.stringify({'status': 'success', 'name': name, 'executionTime': ((endTime - startTime)/1000)}));

                            phantom.exit();
                    }, /*failure*/ function(){
                            console.log(JSON.stringify({'status': 'error', 'message': 'Unable to submit'}));
                            phantom.exit();
                    });

(it uses the jQuery deferred objects)

However, today, when I tried to do the same on my mac but in the other script - I found that setInterval fails :( Current solution is to warp it in the setTimeout, but I'm not a fond of this.

p.s. I found why it was working, because before I called the page.open 2nd time (that was required by the logic of the script). More info - below.

@PavelPolyakov
Copy link

Here is another variant of making the setInterval and setTimeout work.

The minimum viable phantomjs script looks this way:

phantom.page.injectJs('./jquery-1.11.1.min.js');

var page = require('webpage').create();

page.open('http://yandex.ru', function() {
    page.includeJs("http://code.jquery.com/jquery-1.11.0.min.js", function() {

        (function() {
            var dfd = new $.Deferred();

            page.open('http://yandex.ru', function(status) {
                dfd.resolve();
            });

            return dfd.promise();
        })().then(function() {

            var checkNumber = 5;
            var checkInterval = setInterval(function() {
                    checkNumber--;

                    console.log(checkNumber);

                    if (checkNumber == 0) {
                        clearInterval(checkInterval);
                        console.log("exit");
                        phantom.exit();
                    }
                }, 5000);
        });

    });
});

The funny part of it is, that as soon as we would remove another page.open from the code - it would stop working.

So, somehow, 2nd page.open, makes the setTimeout and setInterval works.

This solutions is way worth from the setTimeout(function(){},1); , but both of them are illogical, because I expect that behaviour to work without any workarounds. Would hope that in the next versions of the phantomjs it would work by default.

p.s.
Any ideas why the 2nd page.open makes the timers work?

@simonklb
Copy link

I have this issue as well. No delay on the timeout callback.

@apendua
Copy link

apendua commented Jan 28, 2015

@simonklb Which version of phantomjs are you working with? I thought it was already resolved in one of the recent releases.

@simonklb
Copy link

@apendua I got the build in the Ubuntu repo. Version 1.9.0. Which version is this solved in?

@apendua
Copy link

apendua commented Jan 28, 2015

In that case you're using pretty outdated version. I guess you should try 1.9.8.

@simonklb
Copy link

Looks like it's working, thanks! This should perhaps be marked as resolved then?

@jeffreytu
Copy link

Just a note for users. 1.9.8 still had the setTimeout bug when I tried it. I updated to 2.0 and setTimeout works properly.

@richthofen911
Copy link

The setTimeout still doesn't work on my Fedora 21, phantomjs version 2.0

@ashafer01
Copy link

Still happening for me - PhantomJS 2.0 built from source on RHEL 6.3.

@PavelPolyakov
Copy link

@ashafer01
use http://casperjs.org/ , which laverages the phantomjs. No issues with setTimeout

@MartelBenjamin
Copy link

Still no working for me with Win 7 and phantom 2.0

@coderfantasy
Copy link

https://newspaint.wordpress.com/2013/08/15/phantomjs-window-settimeout-appears-to-be-ignored/

solve my issue

@srm09
Copy link

srm09 commented Jul 20, 2015

Still facing the issue on Mac with phantomjs 2.0.1-dev!!
The mentioned workaround doesn't help either!
My usecase is page.evaluate(), seems to be the case for all callbacks in general!!

@Pistos
Copy link

Pistos commented Jun 28, 2016

Bump. I have some code under test which uses setTimeout. The interval is ignored, the callback executes immediately. I have the callback recurse on itself (it's a classic "wait for" pattern), but the recursion always dies after 3 iterations. Versions:

  • PhantomJS 2.0.0
  • poltergeist 1.9.0 (Ruby gem)
  • Capybara 2.7.1 (Ruby gem)

The exact same spec passes 100% of the time when I have Capybara execute in Firefox instead of PhantomJS.

@Pistos
Copy link

Pistos commented Jun 29, 2016

In fairness, I have tried to set up a minimal example, and could do it. Something is particular to my code (websockets?) which is causing the problem.

@georgiosd
Copy link

Wow, this bug has been open for 4 years and still no resolution?

@frazras
Copy link

frazras commented Apr 28, 2017

Solution:

       setTimeout(function(){
            setTimeout(function(){
                //Your function here
            },700);
        },1);

Double timeouts and it works ¯_(ツ)_/¯

@apendua
Copy link

apendua commented Apr 28, 2017

@frazras I remember using this fix ~3 years ago. It worked for some time and then it broke again with another release of phantomjs. Glad to hear it's working again, but I am also surprised that this issue has not been resolved for so long.

@apendua
Copy link

apendua commented May 2, 2017

@merlinthemagic
Copy link

merlinthemagic commented Jun 24, 2017

I have been struggling with this issue as well. Did some quick tests to see if it was related to load.

Which version of PhantomJS are you using? Tip: run 'phantomjs --version
2.1.1

Which operating system are you using?
CentOS7

Did you use binary PhantomJS or did you compile it from source?
Binary

What steps will reproduce the problem?

Construct a HTML document with a number of Divs. I did it in PHP (see attachment)
generateHTML.php.txt

start a setTimeout loop and have PhantomJS open the html we just generated, vanilla page.open logic:

var count = 0;
function counter()
{
  setTimeout(function()
  { 
    count++;
    counter();
  }, 100);
}
function openPage()
{
  counter();

  var webpage	= require('webpage');
  var page        = webpage.create();
  var sTime       = (new Date).getTime() / 1000;
  var url            = http://devserver.example.com/test.htm
  page.open(url,  function(status) {
    var duration =  sTime  - (new Date).getTime() / 1000;
    console.log(duration);
    console.log(count);
  });
}

openPage();

The count is always 1. I then increase the number of Divs and measure how long it takes PhantomJS to open on localhost + memory consumption. CPU is locked at 100% when opening the page, i get memory from bash.

Here are the results:

2017-06-24_201423

As you can see there is not a linear relationship between the size of the document and load time or memory consumption. Also it appears page.open blocks all other processing.

Opening HTML with ~1200 divs causes PhantomJS to consume 10GB+ memory and almost 5 minutes just to open the page on a gigabit network.

This is the root cause of many exceptions when opening pages. I do not know C++ or i would help find the structure that causes memory and execution time to grow exponentially.

  1. What might be the reason PhantomJS does not scale linearly with the size of the document?
  2. Is it possible to push certain functions off the main thread?

@ghost ghost removed old.Priority-Medium labels Dec 19, 2017
@stale stale bot added the stale label Dec 25, 2019
@stale
Copy link

stale bot commented Dec 28, 2019

Due to our very limited maintenance capacity (see #14541 for more details), we need to prioritize our development focus on other tasks. Therefore, this issue will be automatically closed. In the future, if we see the need to attend to this issue again, then it will be reopened. Thank you for your contribution!

@stale stale bot closed this as completed Dec 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests