Reputation: 71198
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
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.
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.
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.
$.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>');
});
});
was kicked
// $.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
}
);
jQuery.when( deferreds )
Provides a way to execute callback functions based on zero or more Thenable objects, usually Deferred objects that represent asynchronous events.
[...]
For example, the jqXHR object returned by jQuery.ajax() is a Promise-compatible object and can be used [...]
jQuery.get( url [, data ] [, success ] [, dataType ] )
[...] is a shorthand Ajax function [...]
deferred.always( alwaysCallbacks [, alwaysCallbacks ] )
Add handlers to be called when the Deferred object is either resolved or rejected.
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
Add handlers to be called when the Deferred object is resolved, rejected, or still in progress.
Upvotes: 1