i have SendRequest object and that class has a function like
request: function(args)
{
return $.ajax.apply(null, args);
};
then a lot of class use SendRequest object to get server response
var prom = SendRequest.request(
{
type: 'GET',
url: _this.uri
});
return $.when(prom).done(function(response)
{
.... do something
});
My goal is in SendRequest.request need to check on window.localStorage first.
Whether already has a value or not, if has not any value before then send the request.
Otherwise, if already value on localStorage then return $.ajax() object with that saved value before.
request: function(args)
{
var ls = window.localStorage;
var savedResponse = ls.getItem(args.url);
if (savedResponse !=== null)
{
var result = $.ajax();
result.responseText = savedResponse;
result.readyState = 4;
result.status = 'OK';
return result;
}
else
{
return $.ajax.apply(null, args);
}
};
but unfortunately its did not work :(
I've been looking but can not find some case like me
I already try this way to
how to fool jqXHR to succeed always
but its not help much
The reason this doesn't work is that although you've created a fake jqXHR object that's the response from $.ajax, that object isn't actually the parameter that's supplied to the .done callback - it's actually the third parameter.
Also, IMHO you shouldn't really use $.when to "promisify" a single non-promise object. It's intended to handle synchronisation between multiple promises and the fact that it wraps each non-promise into a new promise is just a side effect.
Instead, you should create a new promise that is already "resolved" with the appropriate data:
if (savedResponse != null) {
return $.Deferred(function() {
// retrieve relevant fields from localStorage
...
this.resolve([data, textStatus, jqXHR]);
}).promise();
} else {
// perform real AJAX request
return $.ajax.apply($, args);
}
Alternatively, consider just memoizing the return data itself (for successful calls), not the whole response triple.
You may also find this presentation that I gave at jQuery UK 2013 of use - http://www.slideshare.net/RayBellis/memoizing-withindexeddb
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.
I have the following function to check a users session to see if they're staff or not. Now, I know there are better ways to do this, but I'm trying to make a simple application that's tied with a forum software.
function isStaff(callback) {
$.ajax({
url: url
}).done(function(data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
callback(true);
} else {
callback(false);
}
});
}
Let's say I'm using this function in, like so, when compiling a "post" (Handlebars).
function compilePost(post) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: function() {
isStaff(function(response) {
return response;
});
}
}
var html= template(context);
return html;
}
Problem here, is that the request to check if a user is staff doesn't complete the request until after the function is ran.
I know with Promises is an alternative to async: false, where request is made and the response comes back before the function finishes.
But I have no idea how I can convert this into a promise. I've tried to learn it but I'm stuck at the concept. Can someone explain this to me? Thanks.
First, let's simplify the compilePost function. This function should know how to compile a post in a synchronous manner. Let's change the isStaff fetching to a simple argument.
function compilePost(post, isStaff) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: isStaff
}
var html= template(context);
return html;
}
Now, let's create a new method, with a single purpose - checking if a user is member of the staff:
function checkForStaffMemebership() {
return new Promise(function (resolve, reject) {
$.ajax({
url: url,
success: function (data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
resolve(true);
} else {
resolve(false);
}
}
});
});
}
This function wraps your original ajax call to the server with a promise, whenever the $.ajax call gets a response from the server, the promise will resolve with the answer whether the user is a staff member or not.
Now, we can write another function to orchestrate the process:
function compilePostAsync(post) {
return checkForStaffMemebership()
.then(function (isStaff) {
return compilePost(post, isStaff);
});
}
compilePostAsync finds out whether the user is a staff member or not. Then, it's compiling the post.
Please notice that compilePostAsync returns a promise, and thus if you used to have something like:
element.innerHTML = compilePost(post);
Now, you should change it to something like:
compilePostAsync(post).then(function (compiledPost) {
element.innerHTML = compiledPost;
});
Some notes:
This is only an example, it surely misses some things (proper error handling for example)
The isStaff and checkForStaffMemebership (original and new) do not get any argument, I guess you'd figure out how to pass the userId or any other data you might need
Read about promises, it's a useful tool to have, there is a lot of data about it on the web, for example: MDN.
As per the documentation you dont need to wrap the ajax with a promise which already implements promise. Instead chain the response as explained below.
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the Promise interface, giving them all the properties, methods, and behavior of a Promise (see Deferred object for more information)
You can do something like below by chaining the response:
function isStaff(url, post) {
return $.ajax({
url: url,
dataType:"json"
}).then(function(resp){
//resp = $.parseJSON(resp); /*You dont require this if you have respose as JSON object. Just Specify it in 'dataType'*/
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: resp.is_staff === 1 ? true : false
};
return template(context);
});
}
isStaff(url, post).done(function(template){
/*Your compiled template code is available here*/
}).fail(function(jqXHR, textStatus, errorThrown){
console.log("Error:"+textStatus);
});
Note: Be sure to implement error callbacks also. Because you may never know what
went wrong :)
Simple explanation about promise with $.defer:
For understanding i have created the Fiddle similar to your requirement.
Explanation:
Basically Promise is been introduced to attain synchronous execution of asynchronous JS code.
What do you mean by Async or Asynchronous code?
The code that is executed may return a value at any given point of time which is not immediate. Famous example to support this statement would be jquery ajax.
Why is it required?
Promise implementations helps a developer to implement a synchronous code block which depends on asynchronous code block for response,. like in ajax call when i make a request to server asking for a data string, i need to wait till the server responds back to me with a response data string which my synchronous code uses it to manipulate it , do some logic and update the UI.
Follow this link where the author has explained with detailed examples.
PS: Jquery $.defer implements or wraps promise in quite a different way. Both are used for the same purpose.
let basedataset = {}
let ajaxbase = {};
//setting api Urls
apiinterface();
function apiinterface() {
ajaxbase.createuser = '/api/createuser'
}
//setting up payload for post method
basedataset.email = profile.getEmail()
basedataset.username = profile.getGivenName()
//setting up url for api
ajaxbase.url = ajaxbase.createuser
ajaxbase.payload = basedataset;
//reusable promise based approach
basepostmethod(ajaxbase).then(function(data) {
console.log('common data', data);
}).catch(function(reason) {
console.log('reason for rejection', reason)
});
//modular ajax (Post/GET) snippets
function basepostmethod(ajaxbase) {
return new Promise(function(resolve, reject) {
$.ajax({
url: ajaxbase.url,
method: 'post',
dataType: 'json',
data: ajaxbase.payload,
success: function(data) {
resolve(data);
},
error: function(xhr) {
reject(xhr)
}
});
});
}
A solution using async await in js would be like this:
async function getMyAjaxCall() {
const someVariableName = await ajaxCallFunction();
}
function getMyAjaxCall() {
return $.ajax({
type: 'POST',
url: `someURL`,
headers: {
'Accept':'application/json',
},
success: function(response) {
// in case you need something else done.
}
});
}
I have those two functions where i call "http" from "Count" the "http" return promise. I want to use the return value of "http" in "Count". What I receive now is Undefined !!!
What I'm missing ?
Count Function :
Parse.Cloud.define('count', function(request, response) {
var query = new Parse.Query('MyS');
query.equalTo("Notify", true);
query.notEqualTo ("MainEventCode", '5');
query.find({
success: function(results) {
Parse.Cloud.run('http', {params : results}).then(
function(httpResponse) {
console.log('httpResponse is : ' + httpResponse.length);
response.success('Done !');
}, function(error) {
console.error(error);
});
},
error: function(error) {
response.error(error);
}
});
});
http Function :
Parse.Cloud.define('http', function(request, response) {
var query = new Parse.Query(Parse.Installation);
.
.
.
}
Relying on calling your own functions through an external interface is not a very good practice.
Now that you've realized you're going to need the same code for a different purpose, you should take the time to refactor your code such that you don't need to call the 'http' handler through Parse.Cloud.run():
function doHttp(params) {
// original implementation here
}
Parse.Cloud.define('http', function(request, response) {
doHttp(request.params)
.then(response.success)
.fail(response.error);
}
Parse.Cloud.define('count', function(request, response)) {
var query = new Parse.Query('MyS');
query.equalTo("Notify", true);
query.notEqualTo ("MainEventCode", '5');
query.find()
.then(doHttp) // doHttp will receive the results from `query` as its parameter
.then(function(httpResponses) {
// httpResponses is an array-like object as per the other question:
httpResponses = Array.prototype.slice.call(httpResponses);
httpResponses.forEach(function (response) {
console.log('httpResponse is : ' + response.length);
});
}).fail(response.error);
}
I've taken a look at the other question and as far as the implementation of count goes, I believe you're missing the point that 'http' is returning arguments, which is only an Array-like object.
This should be okay if Parse.Cloud.run runs your function on another virtual machine, but this kind of weird behaviour is another symptom of not refactoring and reusing your code through an external call (an HTTP request inside their infrastructure with JSON passing! It might greatly reduce performance and count against your requests/second quota). If Parse instead does some magic to call your function directly as if it was defined on the same environment, you're going to have problems with it not being an actual Array.
You should modify that function to return a proper array if possible. Parse CloudCode has a version of the Underscore library:
// on http
var _ = require('underscore');
Parse.Promise.when(promises).then(function() {
var results = _.toArray(arguments) // equivalent to Array.prototype.slice above
response.success(results);
}
I think what you're asking is how to use an externally callable cloud function as a step in a bigger cloud procedure. Here's how to do it: (#paolobueno has it essentially correct, with only a couple mistakes in the details).
First, let's convert that 'http' cloud function to a regular JS function. All we need to do is factor out the request and response objects. (#paolobueno has a very good idea to use underscorejs, but I won't here because its another new thing to learn).
// for each object passed in objects, make an http request
// return a promise to complete all of these requests
function makeRequestsWithObjects(objects) {
// underscorejs map() function would make this an almost one-liner
var promises = [];
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
promises.push(makeRequestWithObject(object));
}
return Parse.Promise.when(promises);
};
// return a promise to do just one http request
function makeRequestWithObject(object) {
var url = 'http://185.xxxxxxx'+ object +'&languagePath=en';
return Parse.Cloud.httpRequest({ url:url });
}
It looks like you want the updated cloud function -- rather than use params from the client -- to first make a query and use the results of that query as parameters to the http calling function. Here's how to do that. (Again, using #paolobueno's EXCELLENT practice of factoring into promise-returning functions...)
// return a promise to find MyS instances
function findMyS() {
var query = new Parse.Query('MyS');
query.equalTo("Notify", true);
query.notEqualTo ("MainEventCode", '5');
return query.find();
}
Now we have everything needed to make a clear, simple public function...
Parse.Cloud.define('count', function(request, response) {
findMyS().then(function(objects) {
return makeRequestsWithObjects(objects);
}).then(function(result) {
response.success(result);
} , function(error) {
response.error(error);
});
});
I want to return result directly (Normally we try to put to data and access that data yeah. but now how I want is directly). Like when we call hello() function I want to return result variable.
I try like the following, but it doesn't return yeah. How do I try?
hello: function() {
var result = "";
this.$http.get(window.location.href).success(function(data) {
result = data;
}).error(function (data, status, request) {
});
return result;
}
Looks like it doesn't return the data because the request is async. Your method sets up the request and then returns immediately, synchronously, before the request had a chance to assign any data to the result variable.
You can try to change your code slightly:
hello: function() {
var result = {};
this.$http.get(window.location.href).success(function(data) {
result.data = data;
result.ready = true;
}).error(function (data, status, request) {
});
return result;
}
That should enable you to access the data elsewhere (as result.data), but only after the async query has succeeded. The result.ready flag would tell you if and when that has happened.
In my opinion, it would definitely be better to work with promises, though, e.g. using jQuery Deferreds and promises, or ES6 promises along with a polyfill.
when a function returns a promise, I can call some other function after the first one did it's work:
do_stuff().then(function(){
alert('yoooo');
});
and do_stuff() looks like this:
function do_stuff(){
if(!got_the_data){
var d = $.Deferred();
$.ajax({
success: function(html){
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', function(){
got_the_data = true;
d.resolve();
});
}
});
return d.promise();
}else{
// got the data, what now?
}
}
but what do I return if I already did the ajax request (result is cached) and I don't have to wait for anything? I can't return d.resolve() because the function that was attached to then() won't fire :/
and I can't return d.promise because I have to resolve the "d" somewhere
You can choose between two approaches; caching data or caching promises.
Here's two examples, both of which key on url, though any other key may be used, as appropriate - as long as it uniquely identifies each individual case.
Cache data
var dataCache = {};
function do_stuff_1(url) {
if(dataCache[url] === undefined) {
return $.ajax({
url: url
}).then(function(data) {
dataCache[url] = data;
return data;
});
} else {
return $.when(dataCache[url]);
}
}
Cache promises
var promiseCache = {};
function do_stuff_2(url) {
if(!promiseCache[url]) {
promiseCache[url] = $.ajax({
url: url
});
}
return promiseCache[url];
}
In both approaches, the function will (barring an uncaught error) return a promise, either by executing $.ajax() or by retrieving data/promise from the cache.
In most applications, there's virtually nothing to distinguish one approach from the other.
In an application where the cache is likely to grow to be large, then cache the data and avoid the overhead of caching promise wrappers.
If necessary, the cache can be pre-loaded, thus avoiding the need to fetch known data :
var dataCache = {
'/path/to/data/a': 'A',
'/path/to/data/b': 'B'
}
or
var promiseCache = {
'/path/to/data/a': $.when('A'),
'/path/to/data/b': $.when('B')
}
The simplest solution is to just return an empty already-resolved promise in the else clause:
return $.Deferred().resolve();
To avoid the Promise anti-pattern your code might be better structured thus:
function show_stuff(html) {
return $.Deferred(function(def) {
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', def.resolve);
});
}
function do_stuff() {
if (got_the_data) {
return $.Deferred().resolve();
} else {
return $.ajax(...).then(show_stuff);
}
}
Note that there's no line (yet) setting got_the_data = true - you should consider whether it's really appropriate to wait until the data has been displayed to set this flag, otherwise there's nothing to prevent multiple invocations of do_stuff all resulting in new stuff getting added to #box. IMHO you would be better with a getting_the_data flag.