Omu
Omu

Reputation: 71198

unlike jquery 1 and 2, jquery 3 ajax handler is executing newly added html+js only after completing handler code

I've made a jsbin to show the problem: http://jsbin.com/dexeqiz/edit?html,js,output

having this html:

<div id='log'></div>
<div id='scripts'></div>

and js:

$.get('...', function(){
    $('#scripts')
        .append("<script>$(function(){$('#log').append('<p>3</p>');});<\/script>");
    $('#log').append('<p>1</p>');
    $('#log').append('<p>2</p>');
});

in jquery 1 and 2

it will render in the #log:
3
1
2

but in jquery 3 it will render
1
2
3

(so 3 is added only after the whole ajax handler was completed)

this is a problem because sometimes my code expects that the code that was appended in the line before was executed before calling the next line


right now my only workaround is to put the code after .append(newhtml) inside a setTimeout, but I would prefer not to do that because it looks slightly slower for the user. I would much rather have something like $.when(append).done(function(){code})

UPDATE: seems that this is happening because starting with jQuery 3 scripts for document ready $(function(){}); load async (https://github.com/jquery/jquery/issues/1895) and this is my current solution: http://jsbin.com/xayitec/edit?html,js,output

Upvotes: 3

Views: 258

Answers (1)

Axel
Axel

Reputation: 3323

After all fiddling it stands out: there is no real solution for this issue; at least none that is not very hackish or without changing the whole setup/workflow.

For the sake of completeness I leave my "answers" as they are (see everything below "LOG") and add some background information.

  • https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous

    The document-ready processing in jQuery has been powered by the jQuery.Deferred implementation since jQuery 1.6. As part of jQuery 3.0's alignment with the Promises/A+ standard, document-ready handlers are called asynchronously even if the document is currently ready at the point where the handler is added. This provides a consistent code execution order that is independent of whether the document is ready or not.

  • https://github.com/jquery/jquery/issues/3773#issuecomment-325999054

    Since you're wrapping code in your script tag in $, you can also wrap the other lines:

    $.get('...', function(){ $('#somediv') .append( "somehtml<script>$(function(){$('#log').append('<p>3</p>');});\/script>" ); $(function(){ $('#log').append('<p>1</p>'); $('#log').append('<p>2</p>'); }); });

    I'd try to not rely on such order too much, though, such code can get quite brittle. I think we technically don't guarantee that order is preserved but that's the case right now.

    Often a better solution is to put scripts at the end of body and then wrapping functions in $ is not necessary.

  • https://github.com/jquery/jquery/issues/1895
  • https://github.com/jquery/jquery/pull/2126



One more idea - very hackish

You can search for all scripts in the string that will be appended. Then search for all "$( function(){...} )" occurrences with regex then insert a function like "$( function(){...;executionHandler()} )" that will count down until all these constructs are resolved and then set an outer promise as resovled. But this would be quite hackish as I said at the beginning and also might be quite error prone.



LOG

Version 1, 2, 2.1 and 3 are all tested with jQuery versions 1.12.4, 2.2.4 and 3.2.1 and should work fine with all versions from jQuery 1.8 and above.

Version 1

$.get('...', function(){

    // make shure that $Deferred is in scope of all involved functions
    window.$Deferred = $.Deferred(); // get jQuery deferred object

    $('#scripts').append(
        "hi<script>$(function(){$('#log').append('<p>3</p>');});<\/script>" + // append your common stuff
        "<script>$(function(){$Deferred.resolve();})<\/script>" // add this line to resolve window.$Deferred
    );

    $Deferred.always(function() {
        // execute code after $Deferred was resolved or rejected
        $('#log').append('<p>1</p>');
        $('#log').append('<p>2</p>');
    });

});

Version 2

was kicked


Version 3

// $.get returns a jqXHR object which is a promise-compatible object
$.when($.get('...', function() {
    $('#scripts').append(
        "hi<script>$(function(){$('#log').append('<p>3</p>');});<\/script>"
    )
})).always(function( data, textStatus, jqXHR|errorThrown ) {
    // execute code when $.get is done or fails
    $('#log').append('<p>1</p>');
    $('#log').append('<p>2</p>');
});

Or alternatively

$.when($.get('...', function() {
    $('#scripts').append(
        "hi<script>$(function(){$('#log').append('<p>3</p>');});<\/script>"
    )
})).then(
    function( data, textStatus, jqXHR ) {
        // success callback
    },
    function( jqXHR, textStatus, errorThrown ) {
        // fail callback
    }
);

Details


References

Upvotes: 1

Related Questions