This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 24 days ago.
I'm building a basic hangman game. I have created one class called Hangman and it only has two methods:fetchWord and displayChoices. The fetchWord method takes an url parameter and fetches the word from that specific source. The displayChoices method does what the name says. First, create a list of radio buttons which contains the corresponding letters and display them on the webpage. The only problem with my code is the fetchWord method. Here is the code -- I will explain the issue later.
<body>
<script>
class Hangman {
fetchWord(url) {
fetch(url).then(data=>data.json().then(word=>this.word=word[0]));//fetches the word from a random source.
}
//Create the required elements and put them on the page.
displayChoices() {
const choices = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
choices.forEach(function(choice) {
const label = document.createElement('label');
label.innerText=choice;
label.id=choice;
const element = document.createElement('input');
element.type='radio';
element.value=choice;
element.id=choice;
label.appendChild(element);
document.body.appendChild(label);
});
}
}
const a = new Hangman();
a.fetchWord(' https://random-word-api.herokuapp.com/word ');
a.displayChoices();
document.write(a.word);
</script>
</body>
When you run the code, the radio buttons populate. This is exactly what I want. However, on the bottom of the page, it shows undefined. I think this is an async/await problem, because when I added setTimeout and wrapped the document.write(a.word) in it, it works totally fine. I have written my thought process below in case you want to see it.
First, the class is created .Next, I run the fetchWord method which adds it to the stack.It encounters the fetch API, so it puts it into the browser's call stack space.Now, the fetch API returns the promise, and it was popped out of the stack.The then function is added to the stack -- it runs in the browser's API call stack.When it's done, it puts the callback into the microtask queue.Next, the fetchWord function finally returns and the call stack is empty.Since the call stack is empty, the event loop checks if there are any microtasks that it needs to do. As mentioned earlier, it does. So it runs the callback which was put onto the stack by the then method of Promise.Since this function runs another then, this callback is pushed into the microtask queue.When the callback exits, it is popped out of the stack. Since microtask queues have a higher role than any of the macrotask queues, the event loop keeps running any pending microtasks. As a result, the callback put onto the microtask queue is pushed onto the stack and executed. Once it is done executing, it is popped out of the stack. Next, it runs the displayChoices method. After the displayChoices returns, the document.write is run. Since the callback pushed onto the microtask queue is executed before the document.write, the result (a.word) should be available. However, it is not. How should I construct my code so that it produces the expected result?
The problems have been described in the post.
Related
This question already has answers here:
How to understand LockService and implement it correctly?
(2 answers)
Closed 1 year ago.
My users in App Script read their queue number from a Spreadsheet:
var array = sessionsSheets.getRange(row, 2, 1, 3).getValues()[0];
var questionNumber = array[2]; //This is the variable of interest, it is an integer signifying the queue
sessionsSheets.getRange(`D${row}`).setFormula(questionNumber+1); //This is the updated queue
They then update this queue number as seen above. This method works fine most of the time, but if you take two devices and run the script simultaneously, they both receive the same queue, and the script will halt for one of them because you need a unique queue number later on to use Slides API:
Slides.Presentations.batchUpdate({'requests': requests}, presentationId);
If promises worked, I would have simply put the Slides API line in a try block, and if an error of duplicity pops up, I would then call the same function recursively, up until overlap doesn't occur. However, promises are not supported in App Script, so what should I try instead?
I found a fix for my special case:
I used ScriptProperties in the Properties Services in App Script. When a user opens the application, he looks for the property of the key with his session's token. If it says busy, he waits and recursively tries the same function, if it says free, he changes it to busy, executes his APIs, then at the end changes it to free again.
Code Snippet:
function queueing(comment,name,token,sessionsSheets,row) {
var properties = PropertiesService.getScriptProperties();
var state = properties.getProperty(token);
if (state==null){
properties.setProperty(token , `busy#0`);
posting(comment,name,token,sessionsSheets,row,0);
} else if (state.includes("busy")){
Logger.log('Gotta wait my turn');
Utilities.sleep(2000);
queueing(comment,name,token,sessionsSheets,row);
} else if (state.includes("free")){
var questionNumber = state.split('#')[1];
properties.setProperty(token , `busy#${questionNumber}`);
posting(comment,name,token,sessionsSheets,row,questionNumber);
}}
Good day!
I have sequence of events I would like to happen in order within an onclick function of JavaScript. However, when I add a couple of .post functions that updates a db, it does not work. Is it not possible to have more than 1 .post functions within an onclick function? Here's my code, adding some more text here as the site won't let me post saying I need to add more details because I have posted mostly code. Please help, looking forward to your assistance
$('#vesmos').click(function(){
var dt = new Date();
var dformat = dt.getFullYear() + "/"
+ (dt.getMonth()+1) + "/"
+ dt.getDate() + " "
+ dt.getHours() + ":"
+ dt.getMinutes() + ":"
+ dt.getSeconds();
//set start and end date on form fields
if($.trim($('#stdt').val()) == ''){
$('#stdt').val(dformat);
}
$('#enddt').val(dformat);
//update rt table close rt log (code does not when I add this function)
$.post("closert-docadminus.php",
{
empname: $("#empname").val(),
});
//calculate start and end date
var strt = $('#stdt').val();
var end = $('#enddt').val();
var d2 = new Date(strt);
var d1 = new Date(end);
$('#insec').val(parseFloat(((d1-d2)/1000).toFixed(2)));
$('#inmin').val(parseFloat(((d1-d2)/60000).toFixed(2)));
$('#inhrs').val(parseFloat(((d1-d2)/3600000).toFixed(2)));
//save to ams table logs
$.post("savesettings.php",
{
empname: $("#empname").val(),
task: $("#task").val(),
dttoday: $("#dttoday").val(),
stdt: $("#stdt").val(),
enddt: $("#enddt").val(),
insec: $("#insec").val(),
inmin: $("#inmin").val(),
inhrs: $("#inhrs").val(),
pltform: $("#pltform").val(),
});
//clear textboxes
$('#stdt').val("");
$('#enddt').val("");
$('#task').val("");
$('#pltform').val("");
//fill textboxes
$('#task').val("Mail Open/Sort");
$('#pltform').val("VES");
$('#stdt').val(dformat);
$('#statype').val("Occupancy");
$('#statprio').val("Primary");
// save log to rt table (code does not when I add this function)
save to realtime
$.post("savert-docadminus.php",
{
empname: $("#empname").val(),
task: $("#task").val(),
stdt: $("#stdt").val(),
});
});
You can have enormous events. This is not working because of asynchronous nature of Javascript.
All the I/O calls are asynchronous, so third call can finish first but second call can finish last.
You should learn more about Javascript Event loop.
What you're trying to make is a set of concurrent XMLHTTPRequests
Depending on the browser, you're going to have a set of maximum concurrent XHRs, there's a list here. But even if they are too many, they will get queued and so the browser sends them in the future.
You are trying to make 3 concurrent XHRs in a click event handler, I don't see how that is a problem. Just saying 'it does not work' is not enough information on the problem, you need to dig and debug.
Here's what I recommend you to do:
1, Open the "Console" tab in the developer tools and check that you don't have any JavaScript errors in the console that prevent the XHRs from being dispatched.
2, Open the "Network" tab in the developer tools and check that the XHRs are effectively being dispatched; if they are, check what's the server response.
3, In your .post calls append .done and .fail callbacks in order to confirm what is the server responding, for example:
$.post("some.php", data: { name: "John", location: "Boston" })
.done(function (response) {
console.log(response);
})
.fail(function (jqXHR, textStatus, errorThrown) {
console.log(textStatus);
console.log(errorThrown);
});
That should lead you to the root cause(s) of the problem
Given that the OP clarified in the comments section that 'the problem' is: Dispatching the XHRs sequentially one after another, I'm adding a second answer on how to achieve it, but first, I'm going to try to explain in brief why adding a $.post call after another doesn't work.
XMLHTTPRequests are asynchronous by default. This forces you to think in an asynchronous domain and assume that calling:
asyncFunction1() ($.post)
asyncFunction2() ($.post)
doesn't mean that asyncFunction2 will be executed when asyncFunction1 ends its execution
This doesn't mean that JavaScript is parallel - multi-threaded. JavaScript is single-threaded and asynchronous, it uses an event loop for resolving code execution. Learn more about it here.
Ajax (XMLHTTPRequests - $.post) is a feature that is browser specific... The browser might open a separate thread in order to send the request but it doesn't really matter because remember, at the programming language level (JavaScript) we don't have threads.
Weird right, so back to the issue, What if we want to execute code right after an asynchronous function ends?
There are multiple patterns for achieving this:
Callbacks
Promises
Async/Await
And typically, an asynchronous function should provide you with an API for doing this, at least a good old callback.
According to the jQuery.post ($.post) documentation, you can add a callback like this:
$.post('url', data).done(function (response1) {
// Next code here
})
Which will lead you to:
$.post('url1', data1).done(function (response1) {
// The first XHR finished successfully, we send the second
$.post('url2', data2).done(function (response2) {
$.post('url2', data2).done(function (response2) {
// The second XHR finished successfully, we send the third and so on
$.post('url3', data3).done(function (response3) {...})
})
})
})
Which will eventually lead you to say
"That code nesting doesn't look good and easy to maintain, how can I make it cleaner?"
Congratulations, you've entered the domain of Callback Hells
I am trying to send data from JQuery Ajax to a Generic Handler that calculates something and returns a result. The Ajax request is made inside a for loop at JQuery end. The code looks something like this:
function send(Handler, ids) {
var URL = "http://" + window.location.host + "/Handlers/" + Handler + ".ashx";
var i;
for (i = 0; i < 10; i++) {
var cur = $('.' + ids[i]);
$.ajax({
url: URL,
type: "POST",
data: JSON.stringify({
Data: cur
}),
dataType: "json",
cache: false,
beforeSend: //my before send code,
success: //my success code,
error: //my error code
});
}
alert('Done!');
}
I placed 3 breakpoint in Visual Studio 2012 at line:
$.ajax({
this
alert('Done!');
And third breakpoint at first line in the Generic Handler.
Now, when I try to execute this code, the Ajax works nicely in async way. But, when it reaches the first breakpoint, stops there and then I resume it, instead of reaching the Generic Handler's breakpoint, it continues the loop and goes back to first breakpoint. Then after it reaches the second breakpoint it then stops at generic handler's break point again and again for each value.
So, does that mean that Ajax first collects all the ajax requests and then after the for loop it executes them together?
Javascript is single threaded and non-blocking. This means that in the first iteration won't wait for the ajax call to be completed, it will go back and start the second iteration, and so on.
So, no it doesn't executes them all together. It definately starts the ajax calls in the order of the loop but there is no way to tell what will end first. It might make all the ajax calls and then get an answer (doesn't mean it is the answer of the first iteration), or in the middle of a loop it might be getting answers.
If I am understanding you correctly, you just answered your own question. Ajax is working asynchronously meaning the for loop starts and fires out ajax requests, and continues the loop (the ajax request DOES NOT block)
Therefore it is very likely that the js is performing a loop of code before the request reaches your url (as this has to create a network call)
That said, what are you doing in your beforeSend method? maybe this is making it take enough time that it can perform all iterations of the loop before sending the first request?
To answer your question, no it shouldn't be waiting for the for loop to finish in order to send off the requests, it should be initiating the process as soon as you have made the call
I have a web application which crawls sites in a CMS and looks for data of a certain type.
It works a lot like a recursive file/directory loop:
//pseudo code
var rootWeb = context.site.rootWeb();
var objectThatHoldsAllResults;
recursiveSiteSearch(rootWeb);
function recursiveSiteSearch(webSite) {
//Get all content of a certain type and add to objectThatHoldsAllResults
//Get all SubSites and throw them into a loop that runs recursiveSiteSearch
}
This application lives in the cloud, and has no idea how many subsites live in each CMS that accesses it.
Each time the loop gets all content of a certain type, it makes an AJAX call to the website.
I need to know when the recursion is done, but have no idea how to do so.
Straightforwardly, recursion will have ended when execution falls through to the statement following recursiveSiteSearch(rootWeb);.
However, asynchronicity (ajax) within recursiveSiteSearch may/will(?) mean that some latent activity still exists at that point.
Therefore, you appear to need a mechanism for detecting when all promises (ie. all ajax requests initiated within the recursion) are complete.
jQuery, provides such a mechanism.
pseudo code :
function recursiveSiteSearch(webSite) {
//Get all content of a certain type and add to objectThatHoldsAllResults
//Get all SubSites and throw them into a loop that runs recursiveSiteSearch
//Within the loop, push jqXHR objects onto the externally declared `promises` array.
}
var rootWeb = context.site.rootWeb();
var objectThatHoldsAllResults;
var promises = [];
recursiveSiteSearch(rootWeb);
jQuery.when.apply(jQuery, promises).done(function() {
//statements here will execute when
//recursion has finished and all ajax
//requests have completed.
});
The reason this should work is that jqXHR objects (returned by jQuery.ajax() and its shorthand forms) implement jQuery's Promise interface.
I'm using CasperJS to automate a series of clicks, completed forms, parsing data, etc through a website.
Casper seems to be organized into a list of preset steps in the form of then statements (see their example here: http://casperjs.org/quickstart.html) but it's unclear what triggers the next statement to actually run.
For example, does then wait for all pending requests to complete? Does injectJS count as a pending request? What happens if I have a then statement nested - chained to the end of an open statement?
casper.thenOpen('http://example.com/list', function(){
casper.page.injectJs('/libs/jquery.js');
casper.evaluate(function(){
var id = jQuery("span:contains('"+itemName+"')").closest("tr").find("input:first").val();
casper.open("http://example.com/show/"+id); //what if 'then' was added here?
});
});
casper.then(function(){
//parse the 'show' page
});
I'm looking for a technical explanation of how the flow works in CasperJS. My specific problem is that my last then statement (above) runs before my casper.open statement & I don't know why.
then() basically adds a new navigation step in a stack. A step is a javascript function which can do two different things:
waiting for the previous step - if any - being executed
waiting for a requested url and related page to load
Let's take a simple navigation scenario:
var casper = require('casper').create();
casper.start();
casper.then(function step1() {
this.echo('this is step one');
});
casper.then(function step2() {
this.echo('this is step two');
});
casper.thenOpen('http://google.com/', function step3() {
this.echo('this is step 3 (google.com is loaded)');
});
You can print out all the created steps within the stack like this:
require('utils').dump(casper.steps.map(function(step) {
return step.toString();
}));
That gives:
$ casperjs test-steps.js
[
"function step1() { this.echo('this is step one'); }",
"function step2() { this.echo('this is step two'); }",
"function _step() { this.open(location, settings); }",
"function step3() { this.echo('this is step 3 (google.com is loaded)'); }"
]
Notice the _step() function which has been added automatically by CasperJS to load the url for us; when the url is loaded, the next step available in the stack — which is step3() — is called.
When you have defined your navigation steps, run() executes them one by one sequentially:
casper.run();
Footnote: the callback/listener stuff is an implementation of the Promise pattern.
then() merely registers a series of steps.
run() and its family of runner functions, callbacks, and listeners, are all what actually do the work of executing each step.
Whenever a step is completed, CasperJS will check against 3 flags: pendingWait, loadInProgress, and navigationRequested. If any of those flags is true, then do nothing, go idle until a later time (setInterval style). If none of those flags is true, then the next step will get executed.
As of CasperJS 1.0.0-RC4, a flaw exists, where, under certain time-based circumstances, the "try to do next step" method will be triggered before CasperJS had the time to raise either one of the loadInProgress or navigationRequested flags. The solution is to raise one of those flags before leaving any step where those flags are expected to be raised (ex: raise a flag either before or after asking for a casper.click()), maybe like so:
(Note: This is only illustrative, more like psuedocode than proper CasperJS form...)
step_one = function(){
casper.click(/* something */);
do_whatever_you_want()
casper.click(/* something else */); // Click something else, why not?
more_magic_that_you_like()
here_be_dragons()
// Raise a flag before exiting this "step"
profit()
}
To wrap up that solution into a single-line of code, I introduced blockStep() in this github pull request, extending click() and clickLabel() as a means to help guarantee that we get the expected behaviour when using then(). Check out the request for more info, usage patterns, and minimum test files.
According to the CasperJS Documentation:
then()
Signature: then(Function then)
This method is the standard way to add a new navigation step to the stack, by providing a simple function:
casper.start('http://google.fr/');
casper.then(function() {
this.echo('I\'m in your google.');
});
casper.then(function() {
this.echo('Now, let me write something');
});
casper.then(function() {
this.echo('Oh well.');
});
casper.run();
You can add as many steps as you need. Note that the current Casper instance automatically binds the this keyword for you within step functions.
To run all the steps you defined, call the run() method, and voila.
Note: You must start() the casper instance in order to use the then() method.
Warning: Step functions added to then() are processed in two different cases:
when the previous step function has been executed,
when the previous main HTTP request has been executed and the page loaded;
Note that there's no single definition of page loaded; is it when the DOMReady event has been triggered? Is it "all requests being finished"? Is it "all application logic being performed"? Or "all elements being rendered"? The answer always depends on the context. Hence why you're encouraged to always use the waitFor() family methods to keep explicit control on what you actually expect.
A common trick is to use waitForSelector():
casper.start('http://my.website.com/');
casper.waitForSelector('#plop', function() {
this.echo('I\'m sure #plop is available in the DOM');
});
casper.run();
Behind the scenes, the source code for Casper.prototype.then is shown below:
/**
* Schedules the next step in the navigation process.
*
* #param function step A function to be called as a step
* #return Casper
*/
Casper.prototype.then = function then(step) {
"use strict";
this.checkStarted();
if (!utils.isFunction(step)) {
throw new CasperError("You can only define a step as a function");
}
// check if casper is running
if (this.checker === null) {
// append step to the end of the queue
step.level = 0;
this.steps.push(step);
} else {
// insert substep a level deeper
try {
step.level = this.steps[this.step - 1].level + 1;
} catch (e) {
step.level = 0;
}
var insertIndex = this.step;
while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) {
insertIndex++;
}
this.steps.splice(insertIndex, 0, step);
}
this.emit('step.added', step);
return this;
};
Explanation:
In other words, then() schedules the next step in the navigation process.
When then() is called, it is passed a function as a parameter which is to be called as a step.
It checks if an instance has started, and if it has not, it displays the following error:
CasperError: Casper is not started, can't execute `then()`.
Next, it checks if the page object is null.
If the condition is true, Casper creates a new page object.
After that, then() validates the step parameter to check if it is not a function.
If the parameter is not a function, it displays the following error:
CasperError: You can only define a step as a function
Then, the function checks if Casper is running.
If Casper is not running, then() appends the step to the end of the queue.
Otherwise, if Casper is running, it inserts a substep a level deeper than the previous step.
Finally, the then() function concludes by emitting a step.added event, and returns the Casper object.