I have this code to make an ajax request, but according to Chrome Inspector the callback associated with the request is being called twice (by this I mean the response is being logged into the console twice), 2 more logs are being printed without any content. Here's the code:
var ajax = {
pull: function (settings) {
settings.type = 'get';
settings.callback = typeof (settings.callback) === 'function' ? settings.callback : false;
settings.data = settings.data ? settings.data : null;
return this.request(settings.url, settings.type, settings.callback, settings.data);
},
request: function (url, type, callback, data) {
var ids = ['MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'],
xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
for (var i = 0; i < ids.length; i++) {
try {
xhr = new ActiveXObject(ids[i]);
break;
} catch (e) {}
}
}
if (callback) {
xhr.onreadystatechange = function () {
callback(xhr);
};
}
xhr.open(type, url, true);
if (type.toUpperCase() === 'GET') {
xhr.send();
} else if (type.toUpperCase() === 'POST') {
xhr.send(data);
}
}
}
ajax.pull({
url: 'http://localhost/my/twtools/scripts/ajax.php',
callback: function (xhr) {
console.log(xhr.response);
}
});
xhr.onreadystatechange has several steps (numbered from 0 top 4 I do believe something like 0 = uninitialized, 1 = starting etc, although I can't rember the exact names of the steps anymore, a quick google should find them), and each step is calling your callback. If I remember correctly, the last stage is 4, so I do believe you need to check something like this
if (xhr.readyState == 4 && xhr.status == 200)
{
// call has finished successfully
}
inside you callback, i.e. to check that it is all finished and got a successful response
I've been spoilt by jQuery these days (so much easier to do with jQuery), been quite a while since I wrote raw ajax
You're using onreadystatechange, which gets called more than once (once each state change).
Try using
xhr.onload = function() {
callback(xhr);
};
Related
New to js. I am developing a personal site which uses tribute.js to have an #mention feature. In my case, I need to retrieve the mention list from a remote server. The official document gives an example to implement it. The thing confusing me is the meaning of cb parameter which is not even defined in anywhere. Could anyone help to explain it?
{
//..other config options
// function retrieving an array of objects
values: function (text, cb) {
remoteSearch(text, users => cb(users));
},
lookup: 'name',
fillAttr: 'name'
}
// ajax
function remoteSearch(text, cb) {
var URL = "YOUR DATA ENDPOINT";
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
cb(data);
} else if (xhr.status === 403) {
cb([]);
}
}
};
xhr.open("GET", URL + "?q=" + text, true);
xhr.send();
}
Meaning is hiding in the title of your question, cb means callback :)
It gets called by the remoteSearch function which in its turn was called with cb parameter passed by the tribute.js engine.
Background
I am making a request every 5 seconds using XMLHttpRequest and I want to print my name when I receive the response.
To do this I am using onreadystatechange which allows me to define a callback function when I receive the answer.
Problem
To achieve this, I am using a class. When I first initiate the class I say my name immediately, and then I start a process using setTimeInterval to make a request every 5 seconds and see my name.
The problem is that I see my name the first time, but then I never see it again. The issue is that this seems to be in different context, and thus this.sayMyName() doesn't exist because it doesn't belong to the xhr object.
What I tried
To fix this I tried using scoping by following another StackOverflow question but unfortunately this remains undefined.
Code
class Cook {
constructor() {
// innitial call so we don't have to wait
//5 seconds until we see my name
this.getCookInfo();
setInterval(this.getCookInfo, 5000);
}
getCookInfo() {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
xhr.onreadystatechange = (self => {
return () => {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
self.sayMyName();
};
})(this);
}
sayMyName() {
console.log("Heisenberg");
}
}
Questions:
Is there a way to fix this code without have to pass a context object to the setInterval function?
Note
Kudos++ for those of you who get my reference :P
bind the this.getCookInfo function to this
then you can rally simplify your code
class Cook {
constructor() {
// innitial call so we don't have to wait
//5 seconds until we see my name
this.getCookInfo();
setInterval(this.getCookInfo.bind(this), 5000);
}
getCookInfo() {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
// no need for self gymnastics any more
// using onload, no need to test reasyState either
xhr.onload = e => {
if (xhr.status == 200)
this.sayMyName();
};
// your code lacks one thing
xhr.send();
}
sayMyName() {
console.log("Heisenberg");
}
}
An alternative -
class Cook {
constructor() {
this.getCookInfo();
}
getCookInfo() {
var getit = () => {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
xhr.onload = e => {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
this.sayMyName();
};
xhr.send();
};
getit();
setInterval(getit, 5000);
}
sayMyName() {
console.log("Heisenberg");
}
}
I'm only 99% sure this is right though :p
Is it possible for me to call a function then override the contents of the variable before actually running it?
So I have a function that basically pulls in my Git profile like this:
var GetGitInfo = function() {
var xhr = new XMLHttpRequest();
var gitURL = "https://api.github.com/users/myself/repos";
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
Then I call the function in another step by doing GetGitInfo(); which all works fine.
However, If I wanted to call the function and replace the gitURL variable how would I achieve that?
So something like
GetGitInfo(
gotURL= "https://api.github.com/users/new_user/repo";
);
You can't modify a local variable to a function from outside the function. They are private to the function's implementation.
But, since it's your own function, you can just create an argument that can be passed into the function. You can even make the argument optional so it will take your initial value as the default value if it is not passed.
var GetGitInfo = function(url) {
var xhr = new XMLHttpRequest();
var gitURL = url || "https://api.github.com/users/myself/repos";
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
Then, you can use the function the way you were using it or you can pass in an URL to use:
getGitInfo(); // uses your default URL
getGitInfo("http://someURL"); // uses the URL you pass in
FYI, this function looks like it will ultimately need to either return a promise or accept a callback so you can communicate the results back to the caller.
From the snippet above you need to set the url as a function parameter so when calling it uses the specified url.
var GetInfo = function(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
GetInfo("https://api.github.com/users/myself/repos");
You should do a toString() on the function:
GetGitInfo.toString()
Then you should do a text search and replace on the variable and it's data:
GetGitInfo.toString().substring(0,GetGitInfo.indexOf('somestring'))+'gitUrl="newURL"'+GetGitInfo.toString().substring(.......)
Then you should eval that string!
Or, you know, use function parameters. Either way. Whatever's easiest.
Pass a parameter to the function:
var GetGitInfo = function(gitURL) {
var xhr = new XMLHttpRequest();
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
GetGetInfo("https://api.github.com/users/myself/repos");
Just add a parameter to your function:
var GetGitInfo = function(gitURL) {
var xhr = new XMLHttpRequest();
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
and call it like this:
GetGitInfo("https://api.github.com/users/myself/repos");
Use the parameters
var getData = function(url){
// url can be used here
}
var data = getData("http://apiurl.xy")
Here is the getapps function which loads application names from my website.
getapps = function (applist){
var xmlhttp = new XMLHttpRequest();
var url = "http://mywebsite.com/"+applist;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var myArr = JSON.parse(xmlhttp.responseText);
myFunction(myArr);
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
function myFunction(data) {
var i;
var query = data.data;
if(query != ''){
for(i = 0; i < query.length; i++) {
var appinstall='';
appAvailability.check(
query[i].appid, // URI Scheme or Package Name
function() { // Success callback
appinstall = "is available :)";
console.log(query[i].appid+"is available :)");
},
function() { // Error callback
appinstall = "is not available :(";
console.log(query[i].appid+"is not available :(");
}
);
console.log(appinstall);
}
}
}
}
console.log which is outside appAvailability.check function fires first for n times Within next few seconds console.log which is inside appAvailability.checkfunction fires up for n times with error undefined appid.
I Tested by removing for loop and predefining appid which worked really well without errors.
How can i resolve this by making the loop wait until appAvailability.check is completed ?
It's because appAvailability.check executes a success and a failure callback.
I strongly suspect your code is making a native call in that callback which is running the callback after your callback has executed.
You can get around this using recursion as follows:
function getApps(appslist) {
var xmlhttp = new XMLHttpRequest(),
url = "http://mywebsite.com/"+applist;
callback = callback || function() {};
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var data = JSON.parse(xmlhttp.responseText);
if (data != ''){
checkApp(data.data);
}
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function checkApp(applist, callback) {
var app = applist.push();
if (!app) {
callback();
return;
}
appAvailability.check(
app.appid, // URI Scheme or Package Name
function() { // Success callback
console.log(app.appid + "is available");
// handle availability here
checkApp(applist, callback);
},
function() { // Error callback
console.log(app.appid + "is not available");
// handle absence here
checkApp(applist, callback);
}
);
}
I'm looking to setup a web page that samples data via AJAX calls from an embedded web-server. How would I set up the code so that one request doesn't overlap another?
I should mention I have very little JavaScript experience and also a compelling reason not to use external libraries of any size bigger than maybe 10 or so kilobytes.
You may want to consider the option of relaunching your AJAX request ONLY after a successful response from the previous AJAX call.
function autoUpdate()
{
var ajaxConnection = new Ext.data.Connection();
ajaxConnection.request(
{
method: 'GET',
url: '/web-service/',
success: function(response)
{
// Add your logic here for a successful AJAX response.
// ...
// ...
// Relaunch the autoUpdate() function in 5 seconds.
setTimeout(autoUpdate, 5000);
}
}
}
This example uses ExtJS, but you could very easily use just XMLHttpRequest.
NOTE: If you must have an exact interval of x seconds, you would have to keep track of the time passed from when the AJAX request was launched up to the setTimeout() call, and then subtract this timespan from the delay. Otherwise, the interval time in the above example will vary with the network latency and with the time to processes the web service logic.
I suggest you use a small toolkit like jx.js (source). You can find it here: http://www.openjs.com/scripts/jx/ (less than 1k minified)
To setup a request:
jx.load('somepage.php', function(data){
alert(data); // Do what you want with the 'data' variable.
});
To set it up on an interval you can use setInterval and a variable to store whether or not a request is currently occuring - if it is, we simple do nothing:
var activeRequest = false;
setInterval(function(){
if (!activeRequest) {
// Only runs if no request is currently occuring:
jx.load('somepage.php', function(data){
activeRequest = false;
alert(data); // Do what you want with the 'data' variable.
});
}
activeRequest = true;
}, 5000); // Every five seconds
AJAX, despite the name, need not be asynchronous.
Here is the asynchronous method...
var req;
function ajax(method,url,payload,action)
{
if (window.XMLHttpRequest)
{
req = new XMLHttpRequest();
req.onreadystatechange = action;
req.open(method, url, true);
req.send(payload);
}
else if (window.ActiveXObject)
{
req = new ActiveXObject("Microsoft.XMLHTTP");
if (req)
{
req.onreadystatechange = action;
req.open(method, url, true);
req.send(payload);
}
else
{
alert("Could not create ActiveXObject(Microsoft.XMLHTTP)");
}
}
}
...but here is a synchronous equivalent...
function sjax(method,url,payload,action)
{
if (window.XMLHttpRequest)
{
req = new XMLHttpRequest();
req.open(method, url, false);
req.send(payload);
action();
}
else if (window.ActiveXObject)
{
req = new ActiveXObject("Microsoft.XMLHTTP");
if (req)
{
req.onreadystatechange = action;
req.open(method, url, false);
req.send(payload);
}
else
{
alert("Could not create ActiveXObject(Microsoft.XMLHTTP)");
}
}
}
... and here is a typical action ...
function insertHtml(target)
{
var pageTarget = arguments[0];
if (req.readyState == 4) // 4 == "loaded"
{
if (req.status == 200) // 200 == "Ok"
{
if (req.responseText.indexOf("error") >= 0)
{
alert("Please report the following error...");
pretty = req.responseText.substring(req.responseText.indexOf("error"),1200);
pretty = pretty.substring(0,pretty.indexOf("\""));
alert(pretty + "\n\n" + req.responseText.substring(0,1200));
}
else
{
div = document.getElementById(pageTarget);
div.innerHTML = req.responseText;
dimOff();
}
}
else
{
alert("Could not retreive URL:\n" + req.statusText);
}
}
}