Say you have the following three functions and variable
var someList = [];
makeObject() {
// loops through someList here to create an object
// then calls sendObject function
sendObject()
}
sendObject() {
// sends object to database using HTTP call
}
resetList() {
// resets the list to be empty
// e.g. someList = []
}
Then you call them like so
makeObject()
resetList()
Is there any possiblity or any situation that the list will be reset before the makeObject function has a chance to loop through it?
There are plenty of things you can do in JavaScript which are asynchronous and non-blocking (XMLHttpRequest and setTimeout are classic examples). If you use any of those inside makeObject then resetList will run before the asynchronous parts get called.
resetList() will be called directly after the HTTP call is made. Unless you do other async work before the HTTP call, the order will always be:
makeObject()
loop inside makeObject()
sendObject() is called from inside makeObject()
sendObject() does the HTTP call
resetList() gets triggered right after the HTTP call since that HTTP call is async.
The HTTP returns and any handlers attached to it are triggered.
But make sure that you don't do any other async work, else this will not apply.
Related
I want to call an endpoint using Axios, then do something with the response data and a variable that was defined before the call. I know how to pass data into the callback when creating the function returning the promise myself, but since Axios is external code and I can't seem to find any option to pass extra data in the Axios docs or the config. Is there any other way to make this work, other than sending the value as data to the server and have the server respond it within the response or using an ugly workaround using the window global variable?
const animate = true; // The variable to be passed
axios.get('/endpoint.json')
.then(function(response, animate) { // This doesn't work...
if (response.data.coordinates) {
console.log(animate); // ...because this is undefined
setLocation('takeoff_location', response.data.coordinates, animate); // variable and response data need to be passed to this call
}
});
This creates a new animate variable, entirely unrelated to the one already defined:
function(response, animate)
This new variable "shadows" the one in the higher scope, so within this function any reference to animate only references this new variable. Which isn't defined because Axios (specifically the Promise returned by .get()) has no reason to pass a second value to this callback.
Don't shadow the variable:
function(response)
Then within the function any references to animate will reference the variable in the higher scope.
the .then() is a promise and has access to any variables outside the scope (like a closure). If you want to encapsulate the API call then wrap the entire thing in a function and the arguments go there.
const doRequest = function(animate) {
axios.get('/endpoint.json').then(function(response) {
if (response.data.coordinates) {
setLocation('takeoff_location', response.data.coordinates, animate);
}
});
};
doRequest(250);
doRequest(500);
Note that this will do 2 animation requests quickly in succession. If you want to "wait" for the other one to finish first, you would do this: (note that I'm now returning the axios request from the function). This means the promise object is returned and now you can chain more .then() functions on the "outside" of the function, which will happen only when the request finishes.
const doRequest = function(animate) {
return axios.get('/endpoint.json').then(function(response) {
if (response.data.coordinates) {
setLocation('takeoff_location', response.data.coordinates, animate);
}
});
};
doRequest(250).then(function() {
// this will happen when the outer API call finishes
doRequest(500);
});
In SAPUI5/OpenUI5, I have a JSONModel I populate by a file from server:
var oModel = new JSONModel();
oModel.loadData("http://127.0.0.1/data/config.json");
console.log(JSON.stringify(oModel.getData()));
The console logs undefined since the request is asynchronous.
How to make it synchronous so console.log() is called after the data was loaded?
Using synchronous ajax requests is not recommended as it blocks the UI and will probably result in a warning in the console.
You can attach to the Model.requestCompleted event to access the asynchronously loaded data:
oModel.attachRequestCompleted(function() {
console.log(oModel.getData());
});
The keyword you are looking for is "Deferred"-object --> it enables you to wait for an AJAX request in SAPUI5.
Check this for SAPUI5 context: SAPUI5 Wait for an Deferred-Object // wait for .done() function
Since UI5 version 1.64.0, the API loadData returns a Promise instance:
logLoadedData: async function () {
const jsonModel = new JSONModel();
await jsonModel.loadData("<host>/data/config.json");
console.log(jsonModel.getData()); // after the loadData promise is resolved
},
Alternatively, there is also the API dataLoaded which returns a promise as well. It will resolve when all requests sent by loadData are finished. Here is a syntax without async-await:
doSomethingWith: async function (jsonModel) {
// Not sure if the model has all data loaded? Just use dataLoaded:
await jsonModel.dataLoaded();
console.log(jsonModel.getData());
},
The API loadData is also called internally when the constructor function of JSONModel was called with a string (URL) as an argument. In that case, dataLoaded might come in handy as well.
You can use the attachRequestCompleted-listener from the Model [1]
model.attachRequestCompleted(function(){
console.log(this.getData()); //"this" is the model
});
Another function to use is
$.get(url, function(response){
console.log(response);
model.setData(response);
});
// or
$.ajax(url, {
success: function(){
console.log(response);
model.setData(response);
}
});
This has the advantage that you can configure the request with every setting that jQuery.ajax accepts [2]
Another way to achieve this is to use the attachEventOnce method from EventProvider.
oModel.attachEventOnce("requestCompleted", function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this);
It's best to use this approach when you only need to react to one request, and not all. Otherwise, if you use oModel.attachRequestCompleted(...), all requests will go through the same handler function.
You can also use method chaining to make this a little easier.
oModel.attachEventOnce(...) returns the object that called the method, so you can load your data and handle the callback all in one statement.
oModel.attachEventOnce("requestCompleted", function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this).loadData("http://127.0.0.1/data/config.json");
This will first execute the loadData() request, and then console the response when the request has been completed. It will only use the callback function the first time a request is made. Subsequent requests will not go through the callback function.
If you want ALL requests to go through the SAME callback function, you can do the same thing but using oModel.attachRequestCompleted(...)
oModel.attachRequestCompleted(function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this).loadData("http://127.0.0.1/data/config.json");
This will execute the loadData() request, console the response, and also console the response of all subsequent requests.
NOTE: Be careful using this in the callback functions. If you don't pass this as a parameter of the attachRequestCompleted(...) or attachEventOnce(...) methods, then this will lose it's original context as the controller, and inherit the context of the object calling the function. herrlock's answer demonstrates how the context of this changes.
Event Provider API Reference
Turned out there is a parameter in the .loadData() function to create a sync- call:
oModel.loadData("http://127.0.0.1/data/config.json", "", false);
See API-Reference as well.
I'm not even sure how to phrase this question so I'll have to go with examples. This might not look like useful code here, and indeed it isn't, but it's an example of a problem I've just encountered, stripped down to the bare essentials.
Lets' assume I have
function myObject(params) {
.. do some time-consuming asynchronous stuff
with AJAX and the like ...
return (before all the above is completed);
}
function myFunction(params) {
var doTheSlowStuff = new myObject(params);
}
myFunction(firstParams);
myFunction(moreParams);
What happens to the first myObject when I make the second call to myFunction()? Does it get a chance to complete its work (and if so will it be garbage collected when it has)? Or does it get unceremoniously dumped before it has a chance to finish what it started?
All the time-consuming asynchronous stuff will happen asynchronously :)
That means that the async calls (such as XHR or setTimeout) return instantly and allow execution to continue. In other words, the myObject constructor will return very quickly so there will be no delay between constructing the two myObjects. After both myFunctions return, then finally control will return to the event loop and the JavaScript engine will continue processing events, like mouse clicks, WebSocket events, timers like setTimeout, or XHR requests coming back. Your asynchronous callbacks won't be executed until you return control to the event loop, so don't do anything crazy like
while(true) {
// Check XHR status
}
Don't worry about garbage collection; if you have a DOM event like an AJAX (XHR) request with your myObject in scope then it won't be garbage collected until the event handler itself is garbage collected.
In this specific instance, you will create two instances of myObject which will be retained in memory until your application exits.
You can prove this by running something asynchronously to test this behaviour:
function myObject(params) {
// do something async, like output
// every second ...
var callback = function () {
console.log("I am object " + params);
setTimeout(callback, 1000);
};
callback ();
}
function myFunction(params) {
var doTheSlowStuff = new myObject(params);
}
myFunction(1);
myFunction(2);
// etc.
See a working example at: http://jsbin.com/osEFuWib/1/edit
The asynchronous code has access to a callback function. The callback will run and is independent of any other object. "Losing" the object or creating a new object does not change this. As such, pending asynchronous operations must be explicitly cancelled or the callbacks must be guarded against performing unwanted effects when they are invoked.
The thing about objects in JavaScript is simple: as long as any code - including the callback - can access the object (e.g. assigned to a variable in scope, a window property, or bound to the DOM), they remain accessible. Otherwise, they are unreachable and will be reclaimed at some point.
I have some javascript functions being called on Document Ready:
fogFields();
getLoS();
getShips();
startGame();
getNextMove();
However, it is as though getNextMove() is being called first, most likely as all it does is an ajax call and alerts the result. All the other functions have more work, so, the first thing that happens on load is the getNextMove() alert, and in the background you can see that none of the other functions did their work. Until I click OK on the alert window, no results are shown. Can I make it so that until a function finishes, the next wont even start. Some functions call their own extra functions before they finish, and that works in order, but I cant do that with the whole code...
Given the code in your question, there is no way the call to getNextMove can be invoked before startGame has been exited, regardless of their contents.
It may be true that a function that has been scheduled asynchronously (via timeout, AJAX callback etc.) within startGame completes at any time before or after the invocation of getNextMove, but this is a separate issue. To resolve that issue we need to know more about the contents of the functions.
If the other functions have an AJAX call in them, then these AJAX calls most certainly take a callback argument, which is a function that gets executes, when the AJAX call has finshed. Now, if you want to execute your functions in a way, the one function starts when the AJAX call of the previous function finished, you can add an additional callback argument to your own functions, which will then be passed to the AJAX calls. This should illustrate what I mean:
function logFields(callback) {
ajaxCall(callback);
}
function getLoS(callback) {
ajaxCall(callback);
}
function getShips(callback) {
ajaxCall(callback);
}
function startGame(callback) {
ajaxCall(callback);
}
function getNextMove() {
}
fogFields(function(){
getLoS(function(){
getShips(function(){
startGame(function(){
getNextMove();
});
});
});
});
If all of your functions use a ajax call then just use promises.
Simply return it, for example:
function fogFields(){
return $.ajax();
};
and then just use .then:
fogFields().then(getLos());
more information about deffered object on jquery doc page if you use it.
Or implementation in pure javascript you can find here and more theory here.
or another option, which I will not recommend you is to set async param in $.ajax call to false. Again it's for case you use jQuery.
I'm trying to figure the best way to get my functions executing in the correct order.
I have 3 functions
function 1 - squirts OPTIONs into a SELECT via JSON and marks them as selected
function 2 - squirts OPTIONS into a 2nd SELECT and marks them as selected
function 3 - gets the values from the above SELECTs along with some additional INPUT values, does an AJAX GET resulting in JSON data, which is read and populates a table.
With JQuery Onload, I execute:
function1();
function2();
function3();
I'm finding function3 is executing before the SELECTs have been populated with OPTIONS and hence the table has no results, because the values sent in the GET were blank.
I know this is probably a very simple problem and that there are probably a dozen ways to accomplish this, but basically I need the best way to code this so that function3 only runs if function1 and 2 are complete.
I've come into Javascript via the back door having learnt the basics of JQuery first!
Thanks for your assistance.
Javascript executes synchronously, which means that function3 must wait for function2 to complete, which must wait for function1 to complete before executing.
The exception is when you run code that is asynchronous, like a setTimeout, setInterval or an asynchronous AJAX request.
Any subsequent code that relies on the completion of such asynchronous code needs to be called in such a manner that it doesn't execute until the asynchronous code has completed.
In the case of the setTimeout, you could just place the next function call at the end of the function you're passing to the setTimeout.
In the case of an AJAX call, you can place the next function call in a callback that fires upon a completed request.
If you don't want the execution of the subsequent function to occur every time, you can modify your functions to accept a function argument that gets called at the end of the asynchronous code.
Something like:
function function1( fn ) {
setTimeout(function() {
// your code
// Call the function parameter if it exists
if( fn ) {
fn();
}
}, 200);
}
function function2() {
// some code that must wait for function1
}
onload:
// Call function1 and pass function2 as an argument
function1( function2 );
// ...or call function1 without the argument
function1();
// ...or call function2 independently of function1
function2();
I recommend you use a Promises library. You can hack simple solutions like other answers suggest, but as your application grows, you'll find you are doing more and more of these hacks. Promises are intended to solve these kinds of problems when dealing with asynchronous calls.
The CommonJS project has several Promises proposals which you should check out. Here is a question I asked on SO about Promises a while back with links to other solutions. Learn more about Promises in this Douglas Crockford video. The whole video is good, but skip to just past half way for promises.
I'm using the FuturesJS library currently as it suits my needs. But there are advantages to other implementations as well. It allows you to do sequences very easily:
// Initialize Application
Futures.sequence(function (next) {
// First load the UI description document
loadUI(next); // next() is called inside loadUI
})
.then(function(next) {
// Then load all templates specified in the description
loadTemplates(next); // next() is called inside loadTemplates
})
.then(function(next) {
// Then initialize all templates specified in the description
initTemplates();
});
Even more powerful is when you need to join async events together and do another action when all of the other async events have completed. Here's an example (untested) that will load a bunch of HTML files and then perform an action only once ALL of them have completed loading:
var path = "/templates/",
templates = ["one.html","two.html","three.html"],
promises = [];
$.each(templates, function(i,name) {
promises[i] = Futures.promise();
var $container = $("<div>");
$container.load(path+name, function(response,status,xhr) {
promises[i].fullfill();
}
});
Futures.join(promises, {timeout: 10000}) // Fail if promises not completed in 10 seconds
.when(function(p_arr) {
console.log("All templates loaded");
})
.fail(function(p_arr) {
console.log("Error loading templates");
});
This might be overkill for your application. But if the application is growing in complexity, using promises will help you in the long run.
I hope this helps!
invoke function2 inside of function1 and function3 inside of function2.
It's not clear why f1 and f2 are executing before f3.
Also, are you using the preferred $(document).ready() or some variation of onload?
It might be helpful if you provide a reproducible test case.
fun3() will only run after both are ready. It might run twice. You can fix this with a lock inside fun3() you would need a Singleton to guarantee it works correctly.
var select1ready = false, select2ready = false;
fun1()
{
// do stuff
select1ready = true;
fun3();
}
fun2()
{
// do stuff
select2ready = true;
fun3();
}
fun3()
{
if (select1ready && select2ready)
{
}
}
fun1();
fun2();