Good evening guys -
I've noticed that different browsers seem to handle callbacks differently.
As an example, Firefox seems to let an $.ajax.done({}) callback interrupt the current javascript instruction, but Chrome won't handle the $.ajax.done({}) callback until all current instructions finish. It's like Chrome sends the call to the end of an instruction queue, and Firefox adds it to the top of the instruction stack.
(Bear in mind that this is probably entirely incorrect terminology - I really hope this is the right place to post this)
Explicit Example that outlines my best guess:
function load_a_bunch_of_stuff()
$.ajax({
// ajax things here - e.g. load 10,000 whatevers from a server
}).always(function() {
ajaxStatus = "done!"
});
}
function do_things_with_loaded_stuff() {
// Loops as long as the status is "running" and the User is
// willing to retry:
while (ajaxStatus === "running" &&
confirm("Waiting on ajax, try again?"));
// Do some cool stuff after the $.ajax call finishes
}
// --
// Main: (Assume do_things_with_loaded_stuff() is called before the
// load_a_bunch_of_stuff() finishes)
var ajaxStatus = "running";
load_a_bunch_of_stuff();
do_things_with_loaded_stuff();
-- My best guess --
In this example - the loop will run until Firefox lets the .always({}) change the 'ajaxStatus' to "done!" (probably while the user tries to click on OK) and then we can carry on.
However in Chrome, the .always({}) doesn't ever fire because (I'm guessing) the callback is executed after the current set of instructions finishes. In other words, since the .always({}) is added to the end of the instruction set (rather than in the next slot), it's stuck in the loop and never reaches the .always({}).
This example is just something similar to an issue I've ran into recently while trying to develop between the two browsers. Does anyone know if this interpretation is true?
Can anyone actually explain what's going on?
This is not specific to AJAX, it's just about asynchronous code.
Firefox allows asynchronous callbacks to run while you're in a modal dialog from confirm(), prompt(), or alert(). Chrome, Safari, and Internet Explorer don't.
This can be demonstrated using setTimeout(), it doesn't require $.ajax.
function load_a_bunch_of_stuff() {
setTimeout(function() {
ajaxStatus = "done";
}, 3000);
}
function do_things_with_loaded_stuff() {
// Loops as long as the status is "running" and the User is
// willing to retry:
while (ajaxStatus === "running" &&
confirm("Waiting on ajax, try again?"));
$("div").text("Done");
}
var ajaxStatus = "running";
load_a_bunch_of_stuff();
do_things_with_loaded_stuff();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
Running
</div>
Note that if you take out the confim() call, you should expect the loop to run infinitely on all browsers, since no Javascript engine should allow asynchronous callbacks to interrupt the main flow.
Related
Good afternoon guys -
Is there a well known way to check if a $.ajax call has finished?
-- FOR INSTANCE --
Let's say I'm using a $.ajax call to load in a large number of leaflet polygons from a .geojson file (asynchronously as a layer group). Under normal circumstances, this happens almost immediately - but the user has elected to load a large job this time around. The user has assumed that the set of polygons has been loaded and attempts to do something with this group of layers prematurely - only to find that nothing happens (the layer group doesn't actually exist yet in the browser).
My intuition (I'm new to web development) is to have some sort of global flag that's set for the dependent algorithm to check. We would set the flag prior to loading the layers, and then change it to some other value in the .done({}) section of the $.ajax call.
-- UPDATE --
I've reworked the code to allow users to choose whether or not they wish to retry the request, and also force the browser to wait some time before retrying the request.
However, I've also found the the issue seems to be with Chrome. Firefox appears to be able to handle the $.ajax.always callback as soon as it finishes (in other words, the $.ajax.always callback will interrupt the regular flow of javascript).
Chrome appears to block the $.ajax.always callback and only lets it execute after all other javascript has finished running (no interrupt).
This particular question stemmed from a debug case that automated user input from within a loop - and since Chrome doesn't call the $.ajax.always callback until the current process is complete, the process isn't marked as completed.
Example Code:
procBox = []; // Global scope, stands for Process Box
function loadPolygons() {
procBox["loadPolygons"] = "running";
$.ajax({
// ajax things here
}).done(function() {
procBox["loadPolygons"] = "done";
}).fail(function() {
// failure stuff here
});
}
function dependentFunction() {
if procBox["loadPolygons"] === "done") {
// The meat of the function (dependentFunction things here)
} else {
// Backup case that allows the browser to retry the request
/* --
* If this fires, the server is still trying to process the
* ajax request. The user will be prompted to retry the request,
* and upon agreement, the function will be called again after
* 1 second passes.
*/
var msg = "Oops! It looks like we're still fetching your "
+ "polygons from the server. Press OK to retry.";
if (confirm(msg)) {
setTimeout(dependentFunction, 1000);
}
}
}
This approach seems to work well in Firefox - the alert() stops JavaScript execution and gives it a chance for the .done({}) callback to occur. But for some reason, the while loop never allows the .done({}) callback to complete in Chrome!
Does anyone know of a better approach for this than using flags or async: false?
I appreciate any answers and knowledge out there!
There are numerous ways to do :
as already sugegsted you can use
since you use jQuery, you can use custom events https://learn.jquery.com/events/introduction-to-custom-events/:
$(document).on("myajax:done", function(){ dependentFunction();})
$.ajax({...}).done(function(){
$(document).trigger("myajax:done")
});
or even global ajax events
https://api.jquery.com/category/ajax/global-ajax-event-handlers/
but really consider why not to do something like
procBox = {onLoadPolygons:dependentFunction}; // Global scope
function loadPolygons() {
$.ajax({
// ajax things here
}).done(function() {
procBox["onLoadPolygons"]();
}).fail(function() {
// failure stuff here
});
}
function dependentFunction() {
alert("Please wait for the polygons to load");
dependentFunctionThings();
}
function dependentFunctionThings(){
// Do dependent function things...
}
UPD:
if you ensist on your structure, and still want to use blocking function
use setInterval to perform check
function dependentFunction() {
var h = setInterval(function() {
if (procBox["loadPolygons"] == "done") {
clearInterval(h);
// Do dependent function things...
}
}, 100);
}
Or wrap it up into a Promise (http://caniuse.com/#feat=promises)
function dependentFunction() {
(new Promise(function(resolve) {
var h = setInterval(function(){
if (procBox["loadPolygons"] == "done") {
clearInterval(h);resolve();
}
}, 100);
})).then(function(){
// Do dependent function things...
});
}
But I still believe that something wrong in your structure
From the docs :
.ajaxComplete()
Whenever an Ajax request completes, jQuery triggers the ajaxComplete event. Any and all handlers that have been registered with the .ajaxComplete() method are executed at this time.
http://api.jquery.com/ajaxcomplete/
I have a bootstrapped extension which interacts with the chrome part of Firefox (i.e. even before the content loads), and needs to query an SQLite database for some check. I would prefer a sync call. But, since a sync call is bad in terms of performance and can cause possible UI issues, I need to make an async DB call.
My use case is such:
Make aysnc call to database
Once completed do further processing
Now, this can be easily handled by placing 'further processing' part in handleCompletion part of executeAsync function.
But, I want the 'further processing' to be done irrespective of this statement being executed i.e. This DB lookup may or may not happen. If it doesn't happen well and good, go ahead. If it does I need to wait.
So, I am using a flag based strategy; I set a flag handleCompletionCalled in handleError & handleCompletion callback to true.
In the further processing part, I do a
while(handleCompletionCalled) {
// do nothing
}
//further processing
Is this a good strategy or can I do something better ( I don't really want to use Observers, etc. for this since I have many such cases in my entire extension and my code will be filled with Observers)?
Using a while loop to wait is a seriously Bad Idea™. If you do, the result will be that you hang the UI, or, at a minimum, drive CPU usage through the roof by rapidly running though your loop a large number of times as fast as possible.1
The point about asynchronous programming is that you start an action and then another function, a callback, is executed once the activity is completed, or fails. This either allows you to start multiple actions, or to relinquish processing to some other part of the overall code. In general, this callback should handle all activity that is dependent on the completion of the asynchronous action. The callback function, itself, does not have to include the code to do the other processing. After it has done what needs to happen in response to the async action completing, it can just call another function like doOtherProcessing().
If you launch multiple asynchronous, actions you can then wait for the completion of all of them by having flags for each task and a single function that is called at the end of all the different callback functions like:
function continueAfterAllDone(){
if(task1Done && task2Done && task3Done && task4Done) {
//do more processing
}else{
//Not done with everything, yet.
return;
}
}
This could be extended to an arbitrary number of tasks by using an array, or task queue, which the function then checks to see if all of those are completed rather than a hard-coded set of tasks.
Waiting:
If you are going to have another processing path which executes, but then must wait for the completion of the asynchronous action(s), you should have the wait performed by setting up a timer, or interval. You then yield the processor for a specified period of time until you check again to see if the conditions you need to proceed have occurred.
In a bootstrapped add-on, you will probably need to use the nsITimer interface to implement a timeout or interval timer. This is needed because at the time you are running your initialization code it is possible that no <window> exists (i.e. there may be no possibility to have access to a window.setTimeout()).
If you are going to implement a wait for some other task, you could do it something like:
const Cc = Components.classes;
const Ci = Components.interfaces;
var asyncTaskIsDone = false;
var otherProcessingDone = false;
// Define the timer here in case we want to cancel it somewhere else.
var taskTimeoutTimer;
function doStuffSpecificToResultsOfAsyncAction(){
//Do the other things specific to the Async action callback.
asyncTaskIsDone = true;
//Can either call doStuffAfterOtherTaskCompletesOrInterval() here,
// or wait for the timer to fire.
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
function doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval(){
if(asyncTaskIsDone && otherProcessingDone){
if(typeof taskTimeoutTimer.cancel === "function") {
taskTimeoutTimer.cancel();
}
//The task is done
}else{
//Tasks not done.
if(taskTimeoutTimer){
//The timer expired. Choose to either continue without one of the tasks
// being done, or set the timer again.
}
//}else{ //Use else if you don't want to keep waiting.
taskTimeoutTimer = setTimer(doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval
,5000,false)
//}
}
}
function setTimer(callback,delay,isInterval){
//Set up the timeout (.TYPE_ONE_SHOT) or interval (.TYPE_REPEATING_SLACK).
let type = Ci.nsITimer.TYPE_ONE_SHOT
if(isInterval){
type = Ci.nsITimer.TYPE_REPEATING_SLACK
}
let timerCallback = {
notify: function notify() {
callback();
}
}
var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(timerCallback,delay,type);
return timer;
}
function main(){
//Launch whatever the asynchronous action is that you are doing.
//The callback for that action is doStuffSpecificToResultsOfAsyncAction().
//Do 'other processing' which can be done without results from async task here.
otherProcessingDone = true;
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
Initialization code at Firefox startup:
The above code is modified from what I use for delaying some startup actions which do not have to be done prior to the Firefox UI being displayed.
In one of my add-ons, I have a reasonable amount of processing which should be done, but which is not absolutely necessary for the Firefox UI to be shown to the user. [See "Performance best practices in extensions".] Thus, in order to not delay the UI, I use a timer and a callback which is executed 5 seconds after Firefox has started. This allows the Firefox UI to feel more responsive to the user. The code for that is:
const Cc = Components.classes;
const Ci = Components.interfaces;
// Define the timer here in case we want to cancel it somewhere else.
var startupLaterTimer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
function startupLater(){
//Tasks that should be done at startup, but which do not _NEED_ to be
// done prior to the Firefox UI being shown to the user.
}
function mainStartup(){
let timerCallback = {
notify: function notify() {
startupLater();
}
}
startupLaterTimer = startupLaterTimer.initWithCallback(timerCallback,5000
,Ci.nsITimer.TYPE_ONE_SHOT);
}
Note that what is done in startupLater() does not, necessarily, include everything that is needed prior to the ad-on being activated by the user for the first time. In my case, it is everything which must be done prior to the user pressing the add-on's UI button, or invoking it via the context menu. The timeout could/should be longer (e.g. 10s), but is 5s so I don't have to wait so long for testing while in development. Note that there are also one-time/startup tasks that can/should be done only after the user has pressed the add-on's UI button.
1. A general programming issue here: In some programming languages, if you never yield the processor from your main code, your callback may never be called. In such case, you will just lock-up in the while loop and never exit.
this may be a stupid question, but....is there a way in Javascript to synchronously wait for a specific request to finish without locking the browser thread?
The goal is to call server-side using ajax and executing piece of code after the call is finished AND avoiding callback (hell).
Some simple piece of code like this.
// some js code
var result = doServerCall(); // w/out (b)locking the browser thread -> browser must remain responsive
// some js code to process the result
Please note setTimeout and setInterval is not acceptable solution, what is needed is straightforward execution as above. Eventually a callback after which the execution would continue at the point where the call to server has been done is also ok (see below).
I was using the following in a Firefox Add-on (which is not exactly the thing i want, but is still an acceptable solution).
globalDone = false;
// some js code
doServerCall(); // asynchrnonous call here, the callback is below
var thread = Cc["#mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager).currentThread;
while ( globalDone === false ) {
thread.processNextEvent(true);
}
// some js code to process the result
the callback
function processResponse ( xhrResponse ) {
globalResult = xhrResponse;
globalDone = true;
}
Going through the Internet, StackOverflow and forums everybody seems to want this, yet no browser looks to implement it
You can achieve synchronous HTTP requests, which do not block the UI thread, by making the request from a Webworker. However, communicating the results back to the UI thread, would still be asynchronous. Also, the start-up of a Webworker has time and memory costs, so keep that mind.
Another possibility is using ES6 Generators to simulate non-blocking synchronous execution of async functions. See here. However, browser support for this is still limited.
Webworker Example:
Worker
self.onmessage = function (event) {
if (event.data === "init") {
var xhr = new XMLHttpRequest();
xhr.open("GET", "foo.com", false); // false means non-async
xhr.send(null);
var result = xhr.responseText;
// do stuff with result...
self.postMessage(result); // pass result
self.close(); // terminate self
}
};
Main script
var worker = new Worker("myWorker.js");
worker.onmessage = function (event) {
console.log(event.data);
};
worker.postMessage("init");
Webworkers can also be utilized without needing a separate file, as described here.
this may be a stupid question, but....is there a way in Javascript to
synchronously wait for a specific request to finish without locking
the browser thread?
No, there's no way to do this with synchronous code. The whole purpose of asynchronous code and AJAX is to solve exactly this problem.
I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.
Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.
Here is a pseudo code:
Run JavaScript or jQuery code
Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
Run more JavaScript or jQuery code
My question is, how can I run step 2 the way described above? How do I create/guarantee a mutual exclusion section using JavaScript or jQuery.
I understand the theory (semaphores, locks, ...etc.), but I could not implement a solution using either JavaScript or jQuery.
EDIT
In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.
the code for a critical section would be as follows (using the Boolean variable suggestions):
load_data_from_database = function () {
// Load data from the database. Only load data if we almost reach the end of the page
if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {
// Enter critical section
if (window.lock == false) {
// Lock the critical section
window.lock = true;
// Make Ajax call
jQuery.ajax({
type: 'post',
dataType: 'json',
url: path/to/script.php,
data: {
action: 'action_load_posts'
},
success: function (response) {
// First do some stuff when we get a response
// Then we unlock the critical section
window.lock = false;
}
});
// End of critical section
}
}
};
// The jQuery ready function (start code here)
jQuery(document).ready(function() {
var window.lock = false; // This is a global lock variable
jQuery(window).on('scroll', load_data_from_database);
});
Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:
The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.
Assume two scroll events are triggered right at almost the same moment
Both call the load_data_from_database function
The first event checks if window.lock is false (answer is true, so if statement is correct)
The second event checks if window.lock is false (answer is true, so if statement is correct)
The first event enters the if statement
The second event enters the if statement
The first statement sets window.lock to true
The second statement sets window.lock to true
The first statement runs the Ajax critical section
The second statement runs the Ajax critical section.
Both finish the code
As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.
I think the most helpful information you provided above was your analysis of the locking.
The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one
scroll event is triggered.
Assume two scroll events are triggered right at almost the same moment
Both call the load_data_from_database function
The first event checks if window.lock is false (answer is true, so if statement is correct)
The second event checks if window.lock is false (answer is true, so if statement is correct)
Right away this tells me that you have come to a common (and quite intuitive) misunderstanding.
Javascript is asynchronous, but asynchronous code is not the same thing as concurrent code. As far as I understand, "asynchronous" means that a function's subroutines aren't necessarily explored in depth-first order as we would expect in synchronous code. Some function calls (the ones you are calling "ajax") will be put in a queue and executed later. This can lead to some confusing code, but nothing is as confusing as thinking that your async code is running concurrently. "Concurrency" (as you know) is when statements from different functions can interleave with one another.
Solutions like locks and semaphores are not the right way to think about async code. Promises are the right way. This is the stuff that makes programming on the web fun and cool.
I'm no promise guru, but here is a working fiddle that (I think) demonstrates a fix.
load_data_from_database = function () {
// Load data from the database. Only load data if we almost reach the end of the page
if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {
console.log(promise.state());
if (promise.state() !== "pending") {
promise = jQuery.ajax({
type: 'post',
url: '/echo/json/',
data: {
json: { name: "BOB" },
delay: Math.random() * 10
},
success: function (response) {
console.log("DONE");
}
});
}
}
};
var promise = new $.Deferred().resolve();
// The jQuery ready function (start code here)
jQuery(document).ready(function() {
jQuery(window).on('scroll', load_data_from_database);
});
I'm using a global promise to ensure that the ajax part of your event handler is only called once. If you scroll up and down in the fiddle, you will see that while the ajax request is processing, new requests won't be made. Once the ajax request is finished, new requests can be made again. With any luck, this is the behaviour you were looking for.
However, there is a pretty important caveats to my answer: jQuery's implementation of promises is notoriously broken. This isn't just something that people say to sound smart, it is actually pretty important. I would suggest using a different promise library and mixing it with jQuery. This is especially important if you are just starting to learn about promises.
EDIT: On a personal note, I was recently in the same boat as you. As little as 3 months ago, I thought that some event handlers I was using were interleaving. I was stupefied and unbelieving when people started to tell me that javascript is single-threaded. What helped me is understanding what happens when an event is fired.
In syncronous coding, we are used to the idea of a "stack" of "frames" each representing the context of a function. In javascript, and other asynchronous programming environments, the stack is augmented by a queue. When you trigger an event in your code, or use an asynchronous request like that $.ajax call, you push an event to this queue. The event will be handled the next time that the stack is clear. So for example, if you have this code:
function () {
this.on("bob", function () { console.log("hello"); })
this.do_some_work();
this.trigger("bob");
this.do_more_work();
}
The two functions do_some_work and do_more_work will fire one after the other, immediately. Then the function will end and the event you enqueued will start a new function call, (on the stack) and "hello" will appear in the console. Things get more complicated if you trigger an event in your handler, or if you trigger and event in a subroutine.
This is all well and good, but where things start to get really crappy is when you want to handle an exception. The moment you enter asynchronous land, you leave behind the beautiful oath of "a function shall return or throw". If you are in an event handler, and you throw an exception, where will it be caught? This,
function () {
try {
$.get("stuff", function (data) {
// uh, now call that other API
$.get("more-stuff", function (data) {
// hope that worked...
};
});
} catch (e) {
console.log("pardon me?");
}
}
won't save you now. Promises allow you to take back this ancient and powerful oath by giving you a way to chain your callbacks together and control where and when they return. So with a nice promises API (not jQuery) you chain those callbacks in a way that lets you bubble exceptions in the way you expect, and to control the order of execution. This, in my understanding, is the beauty and magic of promises.
Someone stop me if I'm totally off.
I would recommend a queue which only allows one item to be running at a time. This will require some modification (though not much) to your critical function:
function critical(arg1, arg2, completedCallback) {
$.ajax({
....
success: function(){
// normal stuff here.
....
// at the end, call the completed callback
completedCallback();
}
});
}
var queue = [];
function queueCriticalCalls(arg1, arg2) {
// this could be done abstractly to create a decorator pattern
queue.push([arg1, arg2, queueCompleteCallback]);
// if there's only one in the queue, we need to start it
if (queue.length === 1) {
critical.apply(null, queue[0]);
}
// this is only called by the critical function when one completes
function queueCompleteCallback() {
// clean up the call that just completed
queue.splice(0, 1);
// if there are any calls waiting, start the next one
if (queue.length !== 0) {
critical.apply(null, queue[0]);
}
}
}
UPDATE: Alternative solution using jQuery's Promise (requires jQuery 1.8+)
function critical(arg1, arg2) {
return $.ajax({
....
});
}
// initialize the queue with an already completed promise so the
// first call will proceed immediately
var queuedUpdates = $.when(true);
function queueCritical(arg1, arg2) {
// update the promise variable to the result of the new promise
queuedUpdates = queuedUpdates.then(function() {
// this returns the promise for the new AJAX call
return critical(arg1, arg2);
});
}
Yup, the Promise of cleaner code was realized. :)
You can wrap the critical section in a function and then swap the function so it does nothing after first run:
// this function does nothing
function noop() {};
function critical() {
critical = noop; // swap the functions
//do your thing
}
Inspired by user #I Hate Lazy Function in javascript that can be called only once
I'm trying to write a Sencha Touch 2.0 WebSql proxy that supports tree-data. I started from tomalex0's WebSql/Sqlite proxy. https://github.com/tomalex0
When modifying the script I ran into a strange debugging issue:
(I'm using Chrome 17.0.963.78 m)
The following snipped just got jumped over. The transaction never takes place! But when I set a breakpoint above or below and I run the same code in the console, it does work!
dbConn.transaction(function(tx){
console.log(tx);
if (typeof callback == 'function') {
callback.call(scope || me, results, me);
}
tx.executeSql(sql, params, successcallback, errorcallback);
});
The blue log you can see, the green log is from the success handler. When the query would be performed there would be exactly the same log above (it's a SELECT * FROM ...; so when performing multiple times without changing data I would expect the same result)
I found out that when I add the code block to the watch expressions it also runs.
It isn't being skipped over. It is being scheduled, but not being executed till much later due to the asynchronous nature of the request:
http://ejohn.org/blog/how-javascript-timers-work/
Since the code is being executed synchronously to make the asynchronous call it will delay the call till after the synchronous code has been executed, due to the single threadedness of javascript.