I am trying to store item in an array, newItemList, along with present hour and an object, which I get from a server as JSON response, which I parse into a js object. This simply means that a typical element of newItemList will be [item, 13(present_hour), obj]
Here's the code,
var item;
//some code to obtain item
chrome.storage.local.get({'itemList':[]}, function(item){
var newItemList = item.itemList;
var d = new Date();
if (isItemInArray(newItemList,item) == -1) {
//api call function as callback
apiCall(function(result){
newItemList.push([item,d.getHours(),result]);
});
} else {
var indexOfitem = findItem(newItemList,item);//finding item in the array
if(//some condition){
apiCall(function(result){
newItemList[indexOfTab][1] = d.getHours();
newItemList[indexOfTab][2] = result;
});
} else {
var indexOfitem = findItem(newItemList,item);
storedApiCall(newItemList[indexOfTab][2]);//sending the stored JSON response
}
}
chrome.storage.local.set({itemList: newItemList});
})
function apiCall(callback){
//API call, to obtain the JSON response from the web server
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myObj = JSON.parse(this.responseText);//parsing the JSON response to js object
callback(myObj);
storedApiCall(myObj);//this function just works fine
}
};
xhttp.open("GET", "example.com", true);
xhttp.send();
}
newItemList isn't getting stored in local storage. It contains of only one element, [item, present hour, obj(from present apiCall)]. That's why, only the if part of the code runs each time leading to api calls each time, rendering the else part obsolete.
I read about callback from many famous questions asked around about asynchronous calls, but none connected local storage with callbacks. Before implementing callback, newItemList got stored in local storage, but I couldn't obtain the obj from JSON response for the first time, which is the typical behaviour of asynchronous calls.
Suggest edits, if any.
This line is being executed before the functions given to your calls to apiCall are invoked (i.e. with its initial value item.itemList):
chrome.storage.local.set({itemList: newItemList});
This is because callbacks are typically invoked after some asynchronous action completes. In your case, they will be invoked after whatever operation apiCall performs is complete, e.g. an HTTP response to an API is received.
So if you move that line into those callback functions, at their end after you have mutated newItemList, the set call on chrome.storage.local will be performed with the changes applied (e.g. newItemList.push([item,d.getHours(),result])):
chrome.storage.local.get({'itemList':[]}, function(item){
var newItemList = item.itemList;
var d = new Date();
if (isItemInArray(newItemList,item) == -1) {
//api call function as callback
apiCall(function(result){
newItemList.push([item,d.getHours(),result]);
chrome.storage.local.set({itemList: newItemList});
});
} else {
var indexOfitem = findItem(newItemList,item);//finding item in the array
if(//some condition){
apiCall(function(result){
newItemList[indexOfTab][1] = d.getHours();
newItemList[indexOfTab][2] = result;
chrome.storage.local.set({itemList: newItemList});
});
} else {
var indexOfitem = findItem(newItemList,item);
storedApiCall(newItemList[indexOfTab][2]);//sending the stored JSON response
}
}
})
I think that when the execution arrives on the chrome.storage.local.set({itemList: newItemList}); line, the variable is not set yet because the XMLHttpRequest is async you have to add an chrome.storage.local.set in your callback function or use a promise then set your local storage value
Related
I am new to jQuery and I am trying to build something with AJAX. I tried to use the normal, basic XMLHttpRequest class and then $.ajax({...}) but none of them work.
function getNormal_XHR_Request(){
let request = new XMLHttpRequest();
// I also tried to make a variable here, assign it to the request.response and return it in the end
let returnValue = null;
request.addEventListener(
"load",
(event) => {
if(request.readyState === 4 && request.status === 200){
console.log(request); // this will work
console.log(request.response); // this will work too
// Assign the returnValue to the request.response
returnValue = JSON.parse(request.response); // JSON.parse(request.response) doesn't return undefined, it returns my object that I want to use, but even if I equal the returnValue to it, it will still return undefined
return JSON.parse(request.response);
}
},
false // dispatch event in the bubbling phase not in the capturing phase
)
request.open("GET", "scripts/users.json");
request.setRequestHeader("Accept", "text/json");
request.send();
return returnValue
}
This is the normal XMLHttpRequest() that I used. The problem is, that if I want to say for example
x = getNormal_XHR_Request()
so that I would have the JSON file in my x variable, it doesn't work and it returns undefined.
And the exact same things happens with $.ajax({...})
class XHR_REQUEST{
constructor(path){
this.path = path;
}
getRequest_XHR(requestHeaders){
this.requestHeaders = requestHeaders;
var self = this;
let returnValue = [];
$.ajax({
url : self.path,
dataType : "json",
headers : self.requestHeaders,
})
.done((data) => {
returnValue = data;
})
.fail((jqXHR, errorMessage, error) => {
console.log(jqXHR);
console.log(errorMessage);
console.log(error);
});
return returnValue;
}
};
It works the same as the normal function that I used above, everything that I do, returns undefined, even if request.response gives me the correct answer. If I try to console.log(request.response), I will get the object, if I try to return it and console.log() the result, it will give me back undefined.
Why ?
The result you are getting in both examples is completely normal. The reason you are getting an undefined value is because both requests are happening asynchronously. The correct place where to return something or take some action with the results you get are:
1) request.addEventListener('load', event => {} .... within the event here you can perform the action on a succesfull response.
2) with the $.ajax() call you do it within the done() promise handler.
Those are the right places to take proper action on the results. You have to modify your mindset to start using the responses from the asynchronous callbacks.
The problem there is that you are returning the result, even before the callback has some value filled in the variable.
There are new constructions this days to make asynchronous calls behave synchronously withe async-await constructions. But besides this I think you have to modify or adjust your code to react to the async results in a way it works.
Im currently trying to utilize multiple ajax calls through ajax chaining, but unsure on the best approach since there are a few ways to do this via new frameworks, jquery and pure javascript.
I would prefer to do this with pure vanilla javascript given the native development on js has improved a lot over recent years, however, in the instance of multiple ajax calls, i believe there is still plenty to be improved upon, i believe one of the ways would be to use promises ? i do see many deal with this occurrence via jquery.
i would be very appreciative if fellow coders could give their example as to how they would code a modern approach ajax chain call dependent on ajax returned call values preceding it.
ok, so in short, i am attempting to pass the value of the first ajax call to the second ajax call, along with identifying the correct way of executing the second ajax call.
Below i have added code with comments:
// Establish functionality on window load:
window.onload = function() {
'use strict';
// get product id on load
var pid = document.getElementById('pid');
var colorlist = document.getElementById('colorlist');
var sizelist = document.getElementById('sizelist');
colorlist.onclick = function(e) {
if (typeof e == 'undefined') e = window.event;
var colorid = e.target.value
while (sizelist.firstChild) {
sizelist.removeChild(sizelist.firstChild);
}
// 2ND AJAX CALL
var xhr = getXMLHttpRequestObject();
xhr.open('GET', '/ajax/get_sizes.php?id=' + encodeURIComponent(pid.value) + '&colorid=' + encodeURIComponent(colorid), true);
// set header if sending to php
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(null);
// Function to be called when the readyState changes:
xhr.onreadystatechange = function() {
// Check the readyState property:
if (xhr.readyState == 4) {
// Check the status code:
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status == 304) ) {
var sizes = xhr.responseText;
var sizes = JSON.parse(sizes);
for (var i = 0, num = sizes.length; i < num; i++) {
var label = document.createElement('label');
label.setAttribute ("for", sizes[i].id);
label.classList.add("swatch");
label.innerHTML = sizes[i].size;
var radio = document.createElement('input');
radio.type = "radio";
radio.id = sizes[i].id;
radio.value = sizes[i].id;
radio.name = "sizes";
sizelist.appendChild(label);
sizelist.appendChild(radio);
} //END OF FOR LOOP
} else { // Status error!
document.getElementById('output').innerHTML = xhr.statusText;
}
} // End of readyState IF.
}; // End of onreadystatechange anonymous function.
}; // END OF COLORLIST ONCLICK
// 1ST AJAX CALL
var ajax = getXMLHttpRequestObject();
// Function to be called when the readyState changes:
ajax.onreadystatechange = function() {
// Check the readyState property:
if (ajax.readyState == 4) {
// Check the status code:
if ( (ajax.status >= 200 && ajax.status < 300)
|| (ajax.status == 304) ) {
var colors = ajax.responseText;
var colors = JSON.parse(colors);
for (var i = 0, num = colors.length; i < num; i++) {
var label = document.createElement('label');
label.setAttribute ("for", colors[i].id);
label.classList.add("swatch", colors[i].color);
label.innerHTML = colors[i].color;
var radio = document.createElement('input');
radio.type = "radio";
radio.id = colors[i].id;
radio.value = colors[i].id;
radio.name = "colors";
colorlist.appendChild(label);
colorlist.appendChild(radio);
} // END OF FOR LOOP
} //END OF STATUS CODE CHECK
else { // Status error!
document.getElementById('output').innerHTML = ajax.statusText;
}
} // End of onreadyState IF.
}; // End of onreadystatechange anonymous function.
ajax.open('GET', '/ajax/get_colors.php?id=' + encodeURIComponent(pid.value), true);
// set header if sending to php
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
ajax.send(null);
}; // End of onload anonymous function.
Regards David
Welcome to SO and thank you for your question. I'll do my best to show you some examples of how you could execute your code in a way that might be preferable to you as a solution.
Callbacks
What is a callback?
Simply put: A callback is a function that is to be executed after another function has finished executing — hence the name ‘call back’.
Source of quote
In your code example you want to execute at least 2 HTTP request after one another. This would mean that a piece of code has to be executed twice. For this you can write a function around the XMLHTTPRequest piece to be able to execute it multiple times when writing it down only once.
The function below here has two parameters: url and callback. The url parameter is a string which will be injected in the second parameter of the xhr.open method. The callback parameter will be a function. This function will be called when the request has been succesfully finished.
function get(url, callback) {
var xhr = new XMLHTTPRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
if ('function' === typeof callback) {
callback(xhr.responseText);
}
}
};
xhr.open('GET', url, true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send();
}
Here is a little example of how it would work. See that the callback function has a parameter called data1. That is the xhr.responseText that we got back from the XMLHTTPRequest. Inside the callback function call the get function again to make another request.
get('/ajax1.php', function(data1) {
// Do something with data1.
get('/ajax2.php', function(data2) {
// Do something with data2.
});
});
This is a fairly simple way to make a request after another is finished.
But what if we have 100 requests after each other?
Promisified XMLHTTPRequest
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
Source of quote
Enter Promises. The example below here is almost the same as the example above. Only this time we use a Promise. When calling get we immediately return a Promise. This promise will wait for itself to either resolve or reject. In this case we only use resolve for successful requests. Whenever the request has finished resolve is called and the Promise chain begins.
function get(url) {
return new Promise(resolve => {
var xhr = new XMLHTTPRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
if ('function' === typeof callback) {
resolve(xhr.responseText);
}
}
};
xhr.open('GET', url, true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send();
});
}
So instead of using a callback function we use then. Inside then we do use a callback function which allows us to use the value that has been resolved in the returned Promise. then can be chained with more then's inifinitely until you run out of things to chain.
Inside the callback function call the next request.
get('/ajax1.php')
.then(data1 => {
// Do something with data1.
get('/ajax2.php')
.then(data2 => {
// Do something with data2.
});
});
Fetch
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest, but the new API provides a more powerful and flexible feature set.
Source of quote
Before we've created our own Promise version of an XMLHTTPRequest. But JavaScript has evolved an has gotten new tools to work with. fetch kind of looks like how our get function works, but has way more features and options to make it more powerful. And it also uses promises!
fetch('/ajax1.php')
.then(response1 => response1.text())
.then(data1 => {
// Do something with data1.
fetch('/ajax2.php')
.then(response2 => response2.text())
.then(data2 => {
// Do something with data2.
});
})
.catch(error => console.log(error));
Though the then syntax still makes us nest functions. Like before, what about when you have 100 callback functions to call? That will be a nesting mess!
Fetch + Async/Await
Now this is the way to tackle the nesting problem and make the syntax more like assigning a simple variable in JS. Async/Await is a part of modern JavaScript, so be wary of older browsers not supporting it. Check caniuse for the current support.
The Async/Await syntax works like the following. Create a function with the async keyword in front of it. This will indicate, like it implies, that async code will be performed here. It also makes the function with async before it automatically return a Promise.
Inside the async function use the await keyword whenever you call a function which returns a Promise, like fetch, or our own function get. This will return the resolved value without having to use then or a callback function.
The await keyword also makes the code actually wait before continueing to the next line of code. Now your JS looks nice and can be written with less nesting.
(async function() {
const response1 = await fetch('/ajax1.php');
const data1 = await response1.text();
// Do something with data1.
const response2 = await fetch('/ajax2.php');
const data2 = await response2.text();
// Do something with data1.
}());
I really hope this is helpful and helps you get where you need to be going.
If you have any questions regarding the above, please let me know!
Have a good one!
You can use Promise chain which could be like the example mentioned below:
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);enter code here
You can the documentation mentioned below:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
I have the following function that calls an API several times:
function define(arr, callback) {
var client = [];
var definitions = {};
for (var i = 0, len = arr.length; i < len; i++) {
(function (i) {
client[i] = new XMLHttpRequest();
client[i].onreadystatechange = function () {
if (client[i].readyState == 4 && client[i].status == 200) {
definitions[arr[i]] = client[i].responseText;
}
};
client[i].open('GET', 'http://api.wordnik.com:80/v4/word.json/' + arr[i] +
'/definitions?limit=200&includeRelated=false&sourceDictionaries=webster&useCanonical=false&includeTags=false&api_key=...',
true);
client[i].send();
})(i);
}
return definitions;
}
As explained in How do I return the response from an asynchronous call? the return occurs before the async calls finish causing the function to return an empty object.
Because I have a loop executing many calls I cannot use the solution of status == 200 with a callback that returns what I want.
Things I've tried include making a global variable object and a callback that appends new key value pair to it every time it is called. This doesn't work for the same reason the original code doesn't.
I've also attempted to make a callback that returns the object and passing it to another function that doesn't make the call until a condition is met. This seems like the most promising path but I'm unsure of what condition I could use that would be true only after all async calls are finished as the callback would only have access to variables passed to it as arguments.
You could use fetch and promises for this. Ofc for older browsers you would have to use some polyfill.
const urls = [
'https://raw.githubusercontent.com/fizyk20/generic-array/master/README.md',
'https://raw.githubusercontent.com/fizyk20/generic-array/master/src/iter.rs'
];
function getAll(urls) {
const promises = urls.map(url => {
return fetch(url)
.then(response => response.text()); //or response.json() for json data and
//you can check response.ok property to see if response was successful
});
return Promise.all(promises);
}
getAll(urls)
.then(txts => console.log(txts.slice(0, 400)));
You're almost there.
You only have to check, each time a request finishes/a returned definition is stored in definitions, if the number of results in definitions equals the number of entries in the array arr.
If all results have been stored in definitions call the callback and pass it the definitions
client[i].onreadystatechange = function () {
if (client[i].readyState == 4 && client[i].status == 200) {
definitions[arr[i]] = client[i].responseText;
if (Object.keys(definitions).length === arr.length) {
callback(definitions);
}
}
};
fiddle
This is what I am trying to do:
Request a JSON object from remote server script
WAIT until the JavaScripts gets all the response data
print a value from the response object
I'm trying to use setTimeout in the get config function to recall itself after 1 second if the value is undefined, when it's no longer undefined do something with the value.
Alternatively it seems I could just create some method that loops for some number of seconds, but i'd like to avoid that if there is a better way to accomplish what i'm trying to do? My current code just seems to recurse without any delay which breaks my runtime
Thanks for any ideas, heres the code:
function runApplication() {
var initialiser = new Initialiser();
var config = new Config();
initialiser.fetchConfigurations(config);
config.alertValue('setting1');
}
function Initialiser() {
debug.log("Started");
}
Initialiser.prototype.fetchConfigurations = function(config) {
var req = new XMLHttpRequest();
var url = CONFIGURATION_SERVER_URL;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
var configObject = eval('(' + req.responseText + ')');
config.setConfig(configObject);
} else {
debug.log("Downloading config data...please wait...");
}
}
req.open("GET", url, true);
req.send(null);
}
function Config() {
this.config
}
Config.prototype.setConfig = function(configObject) {
this.config = configObject;
}
Config.prototype.getValue = function(setting) {
if(this.config === undefined) {
setTimeout(this.getValue(setting), 1000);
} else {
return this.config[setting];
}
}
Config.prototype.alertValue = function(setting) {
if(this.config === undefined) {
setTimeout(this.alertValue(setting), 1000);
} else {
alert(this.config[setting]);
}
}
From what I'm looking at, you should just add an extra step to .setConfig, which handles the next piece.
Config.prototype.setConfig = function (data) {
this.config = data;
this.doSomethingAfterTheDataExists();
};
There are lots of ways to do this...
...writing a moderator/observer/pub-sub implementation is dirt-simple, and could be done in relatively few lines for the benefit given...
Writing a Promise system would be even more powerful, but would require more work (because it's like the above implementations, with an added layer of abstraction, to make a really clean and straightforward interface for async).
The other alternative to having .setConfig publish a message, or fire another method, would be to call another method, after .setConfig in the AJAX call.
xhr.onreadystatechange = function () {
// .......
config.setConfig(/* ... */);
config.doSomethingAfterTheDataExists();
};
If you're making multiple AJAX calls, with multiple assignments to multiple properties, and you want to wait until every single call is done, then you really should look into implementing a Promise system.
It will save your sanity and would be well-worth the ~60-100 lines which you could minify into oblivion.
If you're just waiting for that one response to come back, then keep in mind, what happens after the onreadystatechange says that it's finished and the data's there (your success check) is synchronous.
The waiting is the async part.
Once it's delivered, everything (including the stuff inside of that check), goes back to being procedural.
Just use jQuery and getJSON with success/done callback.
You could use a synchronous call instead of asynchronous call for XMLHttpRequest. But this may not be the best option if your your users need to interact with the web page while you wait for a response.
...
req.open("GET", url, false);
request.send(null);
I have the following function:
getTasks: function()
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
var tasks = [];
taskRequest.onload = function() {
var response = JSON.parse(this.responseText),
len = response.length,
i = 0,
t;
for(; i < len; i++)
{
task = response[i];
var newTask = {};
newTask.rowID = i;
newTask.title = task.title;
newTask.description = task.description;
newTask.id = task.id;
newTask.hasChild = true;
tasks.push(newTask);
}
alert(tasks);
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
alert(tasks);
// return tasks;
}
This function is in my controller; I call it in my view when I need to load the data in. However, I wish to return this data so I can assign it to a variable in the view.
Now what happens is that it returns emptiness. The last alert (bottom one) seems to be running too fast and it returns an empty array, while the one that only gets alerted after the onload function is done, contains what I need.
Now my obvious question, how can I get my function to return the array with the data, instead of without?
Putting a timer on it seems hardly the right decision.. Thanks!
"However, I wish to return this data so I can assign it to a variable in the view."
Aside from making the AJAX request synchronous (which you probably don't want), there isn't any way to return the data.
Whatever code relies on the response needs to be called from within the response handler.
Since functions can be passed around, you could have your getTasks method receive a callback function that is invoked and will receive the tasks Array.
getTasks: function( callback ) // receive a callback function
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
taskRequest.onload = function() {
var tasks = [];
// code populating the tasks array
alert(tasks);
callback( tasks ); // invoke the callback
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
}
So you'd use it like this...
myObj.getTasks(function(tasks) {
alert('in the callback');
alert(tasks);
// Any and all code that relies on the response must be
// placed (or invoked from) inside here
some_other_function();
});
function some_other_function() {
// Some more logic that can't run until the tasks have been received.
// You could pass the tasks to this function if needed.
}
You are getting empty alert because when the bottom alert is executed the server response is not available and tasks array is empty.
When the server response comes the tasks array is populated by the code which you have in the onload handler so you see the tasks in the second alert.