In the code below, there are certain cases in which I might find myself on the wrong page and then re-route, if a particular condition is met. In such cases, how would I call an $http.get call from within my code, and wait for the html source code to be retrieved before continuing my script?
function checkUrl(url, size, code) {
return $http.get(url).then(function(response) {
var html = response.data;
var listedPage = utility.countClass(html, 'product-tile');
if(listedPage > 1) {
url = getCorrectPage(html, code);
// this doesn't work, but I want to acquire the html of the newly acquired url
$http.get(url).then(function(response) {
html = response.data;
});
}
stockData.name = getProductName(html);
return stockData;
});
}
How about something like this:
function checkUrl(url, size, code) {
return $http.get(url).then(function(response) {
var html = response.data;
var listedPage = utility.countClass(html, 'product-tile');
if(listedPage > 1) {
url = getCorrectPage(html, code);
return $http.get(url).then(function(response) {
html = response.data;
stockData.name = getProductName(html);
return stockData;
});
}
else {
stockData.name = getProductName(html);
return stockData;
}
});
}
You can't write synchronous I/O in JavaScript. The closest thing to what you want is a Promise.
The problem is that you are creating a logic that is not possible given the asynchronous nature of $http.get(url).
Asynchronous is like you are telling js: listen do this, and when it's finished call this function I'm passing to you.
So you give him this instruction and it delegates it to another thread and continues to run your program immediately.
When that thread returns it will run the function you passed to it. But this is always after the rest of your code has already finished executing.
So it's not possible to do what you want in the way your code is structured.
You have to reformulate so the action you want is contained in the function you pass to $http.get(url).
Related
I have these codes in controller that call webservice using $http get to retrieve some data. Here is the following code:
UPDATE CODES:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
})
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
However, since $http get is asynchronous method, the program will invoke the console log and the codes after immediately and boothDesignatedCoords will be undefined. I do not want that. I want the program to invoke the console log and the codes after ONLY when the webservice consumption is completed. So I did the following using this answer: how to slow down a javascript loop:
go();
function go() {
console.log("hello");
if (boolGetBoothCoords == false) {
setTimeout(go, 1000);
}
else
{
}
}
go()
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// OTHER CODES that uses variable boothDesignatedCoords will be undefined as well
However, I do not know why it will still invoke the console log but the web service consumption is not completed yet, despite using this method. Can someone please help me? Thanks.
setTimeout is asynchronous, so actually you don't really make a difference calling go function.
What will happen is:
call go() function
call setTimeout inside the function - that will schedule go to be called in (roughly) 1s
call console.log immediately after that
What you probably want is to put your console.log in then callback like that:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
return response.data.arrBoothDesignatedCoords;
})
.then(function(boothDesignatedCoords) {
console.log("boothDesignatedCoords ", boothDesignatedCoords );
});
The other option (not recommended) would be to put console.log in the else part of the if statement in your go function.
In this case, you should also define boothDesignatedCoords before the whole code snippet.
The code that suppose to be run after the response, should be invoked after the response.
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(
strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
codeThatUsesTheResponseData();
});
function codeThatUsesTheResponseData() {
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
}
Having trouble to understand how exactly Protractor order of execution works..
If I am having PageObject:InvitePage
And order of execution is defined like this:
InvitePage.EnterUsername()
InvitePage.EnterPassword()
InvitePage.EnterEmail()
InvitePage.Invite();
InviteHelper.waitForEmail()
browser.go(invitationUrl)
...
expect(somecondition)
All page methods are returning protractor promise(for example browser.sendKeys for entering the password)
waitForEmail also returns the promise that I have created using:
protractor.promise.defer()
Problem is that waitForEmail get executed first and methods after it don't wait for waitForEmail to finish, which I expected to be true by creating the promise using protractor method...anyway I found solution to it and it looks something like this:
lastMethodBeforeWaitForEmail.then(function(){
browser.driver.wait(InvitationHelper.waitForEmail(userEmail))
.then(function(recievedUrl){
...
//methods that I want after
expect(someCondition)
});
});
Pretty ugly don't you think?
Is there a way to do this one more nicely, any suggestions?
And which part around async nature of protractor I didn't get? Am I missing something?
getInvitationEmail
var getInvitationEmail = function (emailAddress){
var deferred = protractor.promise.defer();
mailbox.getEmailsByRecipient(emailAddress, function(err, emails) {
if (err) {
console.log('>Fetch email - call rejected');
deferred.reject(err);
}else{
console.log('>Email service fetched.')
deferred.fulfill(emails);
}
});
return deferred.promise;
};
and then waitForEmail
this.waitForEmail = function(email){
var deferred = protractor.promise.defer();
var timeout;
var interval = 3000;
var timePassed = 0;
var recursive = function () {
var message = '>Checking for invitational email';
if(timePassed>0) {
message = message + ":" + timePassed/1000 + "s";
}
console.log(message);
timePassed += interval;
getInvitationEmail(email).then(function(data){
if(data.length>0){
var loginUrl = data[0].html.links[0].href;
if(interval) clearTimeout(timeout);
console.log(">Email retrieved.Fetching stopped.")
deferred.fulfill(loginUrl);
}else{
console.log(">Still no email.");
}
});
timeout = setTimeout(recursive,interval);
};
recursive();
return deferred.promise;
};
In Protractor/WebDriverJS, there is that special mechanism called Control Flow, which is basically a queue of promises. If you have a "custom" promise, in order for it to be in the queue, you need to put it there:
flow = protractor.promise.controlFlow();
flow.await(InviteHelper.waitForEmail());
Or:
browser.controlFlow().wait(InviteHelper.waitForEmail());
One question and one remark.
Shouldn't you put other methods in the ControlFlow if you want to control the execution flow?
JavaScript engines add ; at the end of commands when needed, but it is always better to put them yourself.
In waitForEmail you have defined a promise, but you need to insert it into the controlFlow. As you may know, all of the normal webdriver actions click(), getText(), etc already know how to execute in the right order so you don't have to chain all your promises with .then every time.
... the bottom of your function should look like this
recursive();
return browser.controlFlow().execute(function() {
return deferred.promise;
});
Your ugly solution lastMethodBeforeWaitForEmail.then(function() ... works because it is one way to make sure the waitForEmail promise is executed in the right order, but the above code is the prettiness that you are looking for.
I'm new to node and am having trouble understanding node's async behavior. I know this is a very frequently addressed question on SO, but I simply can't understand how to get any of the solutions I've read to work in my context.
I'm writing this module which I want to return an object containing various data.
var myModule = (function () {
var file,
fileArray,
items = [],
getBlock = function (fileArray) {
//get the data from the file that I want, return object
return block;
},
parseBlock = function (block) {
//[find various items in the block, put them into an "myItems" object, then
//take the items and do a look up against a web api as below]...
for (var i = 0, l = myItems.length; i < l; i ++) {
(function (i) {
needle.post(MY_URL, qstring, function(err, resp, body){
if (!err && resp.statusCode === 200){
myItems[i].info = body;
if (i === (myItems.length -1)) {
return myItems;
}
}
});
})(i);
}
},
getSomeOtherData = function (fileArray) {
//parse some other data from the file
}
return {
setFile: function (file) {
fileArray = fs.readFileSync(file).toString().split('\n');
},
render: function () {
var results = [];
results.someOtherData = getsomeOtherData();
var d = getBlock();
results.items = parseBlock(d);
return results;
}
}
})();
When I call this module using:
myModule.setFile('myFile.txt');
var res = myModule.render();
the variable res has the values from the someOtherData property, but not the items property. I understand that my long-running http request has not completed and that node just zooms ahead and finishes executing, but that's not what I want. I looked at a bunch of SO questions on this, and looked at using Q or queue-async, but with no success.
How do I get this module to return no data until all requests have completed? Or is that even possible in node? Is there a better way to design this to achieve my goal?
The problem in your example is your calling getBlock() but you have declared your function as getBlockData(). So you will not get a result. Try changing it to both the same.
Presuming that you have them both the same, your next problem is that your processing data from a file, so I presume that your reading the contents of the file and then parsing it.
If this is the case then there are sync reads that you can use to force sync, however I wouldn't recommend this.
You really want to structure your program based on events. Your thinking in the paradigm of 'call a function, when it returns continue'. You need to be thinking more along the lines of 'call a process and add a listener, the listener then does reply handling'.
This works very well for comms. You receive a request. You need to reply based on contents of file. So you start the read process with two possible results. It calls the completed function or the error function. Both would then call the reply function to process how to handle a reply for the request.
It's important not to block as you will be blocking the thread via which all processes are handled.
Hope that helps, if not add some comments and I will try and elaborate.
Have a look at this answer to another question to see a good example of processing a file using the standard listeners. All async calls have a listener concept for what can happen. All you need to do is pass a function name (or anon if you prefer) to them when you call them.
A quick example (based on node.js stream.Readable API:
fs.createReadStream(filename, {
'flags': 'r'
}).addListener( "data", function(chunk) {
// do your processing logic
}).addListener( "end", function(chunk) {
// do your end logic
response(...);
}).addListener( "error", function(chunk) {
// do your error logic
response(...);
}).addListener( "close",function() {
// do your close logic
});
function response(info) {
}
I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.
Not sure if my question is subjective/objective but as a JavaScript newbie i'm encountering this problem quite a lot. So here I go.
I'm used to write C#, so my JavaScript structure looks like C#. And just that, that gives problems I think ;-)
Let's give a simple example where I met my problem again today:
MyLibrary.fn.InitAddEntityForm = function () {
$('a#btnAddEntity').click(function () {
//post data and receive object with guid and isPersisted boolean
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png");
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
};
//////*****/////
//SOME FUNCTION THAT SENDS MY FORM AND RETURNS AN OBJECT WITH TRUE VALUE AND POSTED ENTITY ID
/////*****//////
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
}, "json"
);
return persistedObject;
};
Okay, thats it. Everything looks okay right? Browser says no.
I tried to debug it using firebug, looping over my code line by line, and that way the browser does what I want: Execute a new function to show the next panel in my wizard.
After placing a lot of Console.logs() in my code I figured out that this must be something about timing in JavaScript. In C# the code executes line by line, but apparently JavaScript doesn't.
By placing that Console.log("test") I noticed that "test" appeared in my console before "Post status: Success!".
So here's my question, how should I write my JavaScript code so I have control over the way the browser executes my code?
Should I really replace the code below to the end of my CheckAndSendAddEntityForm()?
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("fout");
}
Is this how I have to write JavaScript: One big domino effect or am I just doing something wrong?
$.post is a shortcut for an AJAX call, AJAX is by definition asynchronous, which means it won't wait on a response before continuing processing. If you switch it to a regular AJAX() method, there is an async option you can set to false, which will make it behave as you are expecting.
Alternatively you can also define a function to execute on successful return of the AJAX request, in which you can call the next step in your process chain.
The AJAX call is asychronous; that means that the callback method exposes by $.post will be executed when the request completes, but your javascript will continue executing as soon as the invoke to $.post finishes. If you want to do something after the ajax call is done, you need to provide a callback method and do something else, ex:
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl, callback) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
callback(); // This is where you return flow to your caller
}, "json"
);
};
Then you invoke like so:
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png", function()
{
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject .gdPronoId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
JavaScript is single-threaded. If you have asynchronous functionality, a simple boolean semaphore variable will help not to allow invocations of a function while some processes are running.
If you want to execute asynchronous tasks one by one (like a domino line), you will need to use callback functions.
What you're encountering is the "asynchronous" bit of AJAX. If you want to physically (as in the line line by line in the Javascript file) you can use the .success,.pipe or .done jQuery methods to add a callback to process the data further. Don't embed your callbacks if you can help it, or you will get a "domino effect" as you call it.