jQuery .when not working as expected with rest operator - javascript

So in our code base we are using jquery just for the ajax section of the codebase but we want to wrap all of our calls so if we wanted to eventually get rid of jquery then we would only have to change the implementation. Here is the definition of the wrapper.
export const getAll = (...ajaxCalls) => {
// return $.when($.ajax(ajaxCalls));
return $.when(ajaxCalls.map(call => $.ajax(call)));
}
And here is the where we are calling it.
getAll(`/response/${customerId}`, `/response/${methods}`).done((response1, response2) => {
console.log("getAll1",response1);
console.log("getAll2",response2);
})
However the response is looking something like this:
My understanding of when would be that response1 should contain the responseBody of response1 and response2 should contain the responseBody of response2 but that doesnt seem to be the case. What am I missing?

It seems that you are logging jqHXR objects, which are probably a consequence the odd way that jQuery.ajax() and jQuery.when() interact to deliver results see last but one example here.
In anticipation of purging jQuery at a later stage, I venture to suggest that you need to standardize all your calls at this stage. This is fairly simple, just use .then() instead of .done() and expect results to be delivered in an Array:
getAll(`/response/${customerId}`, `/response/${methods}`)
.then(results => {
console.log("getAll0", results[0]);
console.log("getAll1", results[1]);
});
You then need to jump through a few hoops in getAll() in order to standardize various aspects of jQuery.ajax() and jQuery.when() behaviour:
standardize jQuery.when()'s delivery results as individual parameters rather than Array.
standardize jQuery.ajax()'s delivery of (data, statusText, jqXHR) to its success path.
standardize jQuery.ajax()'s delivery of (jqXHR, statusText, errorThrown) to its error path.
standardize the behaviour of jQuery.ajax()'s error handler across different versions of jQuery.
export const getAll = (...ajaxCalls) => {
function makeAjaxCallAndStandardizeResponse(call) {
return $.ajax(call)
.then(
// success handler: standardize success params
(data, statusText, jqXHR) => data, // discard statusText and jqXHR
// error handler: standardize error params and ensure the error does not remain "caught".
(jqXHR, textStatus, errorThrown) => $.Deferred().reject(new Error(textStatus || errorThrown)).promise(); // discard jqXHR, and deliver Error object on the error path.
);
}
// Aggregate with Promise.all() instead of jQuery.when() to cause a javascript Promise to be returned and results to be delivered as Array.
return Promise.all(ajaxCalls.map(makeAjaxCallAndStandardizeResponse));
}
The success handler is probably unnecessary as the Promise.all() aggregation would automatically cause statusText and jqXHR to be discarded, but no real harm in making those discards explicit, unless you need to be obsessive about milliseconds.
Returning $.Deferred().reject(new Error(textStatus || errorThrown)).promise() from the error handler should give the same behaviour in all versions of jQuery, causing an Error object to be delivered on the error path. (Logged error messages will differ though) Be sure to test this.

Related

Where is the documentation on res.on('end'

Just cant find it! looking for documentation on the following on error, data, and end
return new Promise((resolve, reject) => {
https.get(setUrl(substr), (res) => {
let data = ''
res.on('data', (d) => {
data += d
});
res.on('end', () => resolve(data));
}).on('error', reject);
});
In the doc for https.get(), it says to go look at the doc for http.get() for the callback parameters.
There it says:
The callback is invoked with a single argument that is an instance of http.IncomingMessage.
And, if you then go look at the doc for http.IncomingMessage, you find that:
It implements the Readable Stream interface, as well as the following additional events, methods, and properties.
In that doc, you will find a description of the data and end events.
The https.get() itself returns an http.ClientRequest object and in the doc for http.request() which https.get() is derived from, it explains:
If any error is encountered during the request (be that with DNS resolution, TCP level errors, or actual HTTP parse errors) an 'error' event is emitted on the returned request object. As with all 'error' events, if no listeners are registered the error will be thrown.
Yes, it is work to follow all this. This is one of the aspects of object oriented design with lots of derived and common objects. You have to find the object being used or perhaps even the base object and then go look in its doc for find out how to use it.

Rejecting Javascript Promises And Error Handling

I'm trying to wrap my head around the correct way to indicate a failure within a .then().
If the promise doesn't fail, (I.e. the operation that returns the promise does it' job properly, such as an AJAX request that returns a status 200), but I decide that the result isn't valid, usually I'd do a popup, explaining the issue to the user, and do a "return false;" to exit the method early.
However, with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead, and presumably let this get caught by the .catch() that I've chained on.
My concern is that I want to distinguish between an operation within a promise that succeeded, but which I don't like the result of, and a failed operation within a promise.
For example, if I perform an AJAX call and it fails with a 404, that's not really recoverable, so it seems appropriate to reject with a popup saying something like "something went wrong".
However, if the AJAX request is successful (returns a status 200), but the response indicates that something isn't right (Like the user didn't fill out a field with a correct value), then I'd like to handle that a certain way, which might involve not just a popup with a message (e.g. maybe DOM manipulations, red text etc, things that I might not want to do if it's a 404).
Below are 2 examples to better explain what I mean.
The first being the original implementation with callbacks and the second being with promises (Wrapping the ajax call with the Q promise library to make it a proper promise).
Callback version:
$.ajax({
url: "/cars/1",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json"
})
.done(function (data) {
if (!data.IsSuccessful) {
//The request was successful (status 200), but the server is returning IsSuccessful=false
alert(data.Message);//message says something like "we have that car in our catalogue but we don't have it in stock"
return false;//early exit from .done()
}
//if it gets here, everything is good and I can do something with the result
})
.fail(function (data) {
//The request actually failed due to a generic status 500 error which has something I don't necessarily want to expose in a popup to the user
alert("Something went wrong");
});
Promise version:
var myPromise = Q(
$.ajax({
url: "/cars/1",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json"
})
);
myPromise.then(function (data) {
if (!data.IsSuccessful) {
throw new Error(data.Message);
}
//all good, lets do something with the result
})
.catch(function (error) {
//what is error?
//How do I know if it's one that I want to show to the user or not?
}).done();
In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?
If data.IsSuccessful==false, then it will also end up in the .catch()?
What if I want to treat both failures differently, how would I go about that?
I'm not calling resolve or reject anywhere, is that problematic?
I'd like to make sure I'm following best practices as much as possible.
TL;DR: You can use rejections for control flow, but in general they are for exceptional cases only
In the promise version, if the request returns a 404 it will end up in the .catch() immediately right?
Yes.
If data.IsSuccessful==false, then it will also end up in the .catch()?
Yes.
I'm not calling resolve or reject anywhere, is that problematic?
Not at all. You're not using the Promise constructor (which you don't need, as you already have a promise for the ajax result).
What if I want to treat both failures differently, how would I go about that?
Throw different kinds of errors so that you can distinguish them. Give them names, add special properties, do subclassing (in ES6 especially), or just look at the message.
with promises, if from within the .then(), I want to do something similar, I've been lead to believe that what I should do is throw an error instead
Not necessarily. You can do exactly the same as you did without promises - put an if-else in the callback (or an if with an early return if you prefer).
In terms of control flow and their result values,
.then(function(res) {
if (!res.isOK) {
// do something
return false;
}
// do something else
}).catch(function(err) {
// handle something
})
and
.then(function(res) {
if (!res.isOK)
throw new MyError(res);
// do something else
}).catch(function(err) {
if (err instanceof MyError) {
// do something
return false;
}
// handle something
})
are pretty much equivalent (except for exceptions in do something and with code other than this throwing MyErrors). The major difference is when you want to chain additional .then(…) invocations between the then and catch. If you don't, just choose whatever you like better.

How to know which $.ajax promise has resolved?

I'm using jQuery ajax to request data which will then be made into different kinds of charts or tables.
I've put the queries I want to run into an object and send the requests. runQuery() returns a jQuery promise. The data returned when the promise is done is correct. [Edit]Since the ajax requests run asynchronously they may not come back in the order they were issued [/EDIT] and I have no way of know which query the returned data was for.
function refreshData(){
for(var key in baseQueries){
runQuery(baseQueries[key])
.done(function(data){
console.log("Query data for "+key);
// want to call different charting functions
// based upon which results are returned
});
}
};
runQuery(obj) { // "public" function
var params = $.extend({},defaults,obj);
return sendRequest(queryUrl,params)
}
sendRequest(url,data,method){ // "private" function
method = method || "GET";
return $.ajax({
type:method,
url:url,
dataType:"json",
data:data
})
.fail(function(error){
console.log(error);
});
}
In this case the console logs the value of key during the last iteration over the baseQueries object. For example if there are three items in my baseQueries object and the the last item in my baseQueries object is
"This-is-the-last-query":"queryparam1,queryparam2,etc"
Then when all three of the ajax calls resolve I get three logs of "This-is-the-last-query". Which is not helpful because it doesn't tell me which query the data belongs to.
This is similar to the idea of the infamous javascript loop issue but I really don't see how the answer of attaching the value to a DOM element could be used here.
How do I match up which query call goes with which promise? How to I pass the key through the ajax call and return it with the promise data.
Edit
Don't think this is a duplicate of the indicated thread. I see how they are related, but not how to use that to solve this. Suggested duplicate doesn't mention jquery, ajax, promises, or asynchronous issues. It is also marked as a duplicate for another thread that doesn't mention any of those things either.
The solution shown either involves using the dom element to hold the information (which doesn't apply here) needed for the onclick or by adding a closure, but I don't see how to do that when there is already data being returned.
If you pass jQuery ajax a parameter that it knows nothing about, it ignores it and you can access it later.
Here we set a parameter for your extra value (mykey) then we have access to it later for the instance we are using:
function refreshData() {
for (var key in baseQueries) {
runQuery(baseQueries[key], key)
.done(function(data) {
console.log("Query data for " + this.myKey);
// "this" here is the ajax for which the promise (ajax) gets resolved,
// and we return that promise so it works here.
// want to call different charting functions
// based upon which results are returned
});
}
};
runQuery(obj,key) { // "public" function
var params = $.extend({}, defaults, obj);
return sendRequest(queryUrl, params,,key)
}
sendRequest(url, data, method, key) { // "private" function
method = method || "GET";
return $.ajax({
type: method,
url: url,
dataType: "json",
data: data,
myKey:key
})
.fail(function(error) {
console.log(error);
});
}
if you want to check if jquery promise is resolved you can check with jqueryPromise.state() which returns either pending, resolved or rejected depending on state.
If you are sending multiple ajax requests and you want to know when they are completed you can put them into array ([$.ajax(...),$.ajax(...)]) and pass it to
$.when like
var requests = [$.ajax(...),$.ajax(...)];
$.when.apply($, requests).done(function() {
console.log('all requests completed');
});
if you want to build something complex with promises I would suggest using bluebird or less complex rsvp

Breeze entityManager calling fail callback even on success

The entityManager instance is calling fail callback even on success.
Checking the network activity, the requests returns 200 and with expected data.
If I remove the .fail() method from the chain it works just fine, with it I get that the error object is undefined.
My WebAPI is running with CORS enabled and as we're in early development, everything is fully allowed (headers, methods, any origin, credentials).
Here is the JavaScript code:
function getResumoPromocoes() {
var resumoPromocoes = [];
var orderBy = "visualizacoes";
return EntityQuery.from("Promocoes")
.select("id, titulo, descricao, iniciaEm, expiraEm")
.orderBy(orderBy)
.toType("Promocao")
.using(manager)
.execute()
.then(function(data) {
resumoPromocoes = data.results;
log("Resumo das Promoções recebidas", resumoPromocoes.length, true);
return resumoPromocoes;
})
.fail(_queryFailed(error));
}
Is it proper to pass the fail callback after the success one in the .then() method? Same thing with .fail()?
Not sure if this is the issue but the ";" after the then clause is almost certainly an error and the argument to '.fail' needs to be a function not the result of executing a function. So try this instead.
.then(function(data) {
...
})
.fail(_queryFailed);

Wrapping jQuery $.ajax behind a facade in q.js without refactoring

It's possible this should be on code review, but here we go!
I have a fairly large application with a lot of ajax calls. I started using Q for some async stuff, and figured I would wrap the ajax calls in Q to ensure all async methods have the same signature.
I'm using a global facade method, so my ajax calls look like:
App.ajax( config ).then( doWhatever );
with App.ajax looking something like this:
ajax: function( config ){
var ajaxReturn = $.ajax( config );
ajaxReturn.error(function( xhr ){
// some custom error handling
});
return ajaxReturn;
}
I modified App.ajax to look like this:
ajax: function( config ){
var dfd = Q.defer();
var ajaxReturn = $.ajax( config );
ajaxReturn.done(function( results, status, xhr ){
delete xhr.then;
dfd.resolve( results );
});
ajaxReturn.error(function( xhr ){
// some custom error handling
dfd.reject( "some message generated by error handling" );
});
return dfd.promise;
}
This works, 0 changes needed on individual ajax calls themselves, but it caused the Ajax calls that cared about the "status" and "xhr" parts of the $.ajax return to stop working.
I've read Q's docs about coming from jQuery, and it basically just suggests "you should treat promise-returning functions like normal synchronous functions, in that you should assume they only return a single object"
Now, I didn't want to have to refactor all the calls that care about the xhr object to either take an object or use spread instead of then/done. So I added this code right before returning the Q promise:
dfd.promise.then = function( callback ){
dfd.promise.then = Q().then;
return dfd.promise.spread( callback );
};
and changed my resolve line to:
dfd.resolve( [ results, status, xhr ] );
and now App.ajax looks like this:
ajax: function( config ){
var dfd = Q.defer();
var ajaxReturn = $.ajax( config );
ajaxReturn.done(function( results, status, xhr ){
delete xhr.then;
dfd.resolve( [ results, status, xhr ] );
});
ajaxReturn.error(function( xhr ){
// some custom error handling
dfd.reject( "some message generated by error handling" );
});
dfd.promise.then = function( callback ){
dfd.promise.then = Q().then;
return dfd.promise.spread( callback );
};
return dfd.promise;
}
So this is over-riding this specific Deferred's then function with a wrapper function that will reset then to the "real" then and use spread instead for this call. This results in my App ajax calls being able to retain their original signature, but still be a Q promise through and through.
This seems to work fine, and that's great.
now, finally, for the questions But is this advisable? I understand there's minor performance implications in unnecessarily creating the extra Promise in the custom "then" method. I don't care about that. I'm more wondering if there's some gotcha that hasn't bit me yet where this is strictly a very bad idea.
Edit:
There are some problems with the above block. This is the most up to date, most correct block of code. The promise's then can't just be reset to be the 'real' then, because subsequent calls to the initial promise don't get the proper spread result, so we have to reset the then method back to the over-ridden one that calls spread before returning the new spread promise.
ajax: function( config ){
var dfd = Q.defer();
var ajaxReturn = $.ajax( config );
ajaxReturn.done(function( results, status, xhr ){
delete xhr.then;
dfd.resolve( [ results, status, xhr ] );
});
ajaxReturn.error(function( xhr ){
// some custom error handling
dfd.reject( "some message generated by error handling" );
});
var cachedThen = dfd.promise.then;
dfd.promise.then = function overrideThen( fulfilled, rejected ){
dfd.promise.then = cachedThen;
var spreadPromise = dfd.promise.spread( fulfilled, rejected );
dfd.promise.then = overrideThen;
return spreadPromise;
};
return dfd.promise;
}
You're overthinking it. Q is designed to interoperate with jQuery promises.
If you want to convert a function to return a Q promise - just wrap it with Q():
myAjaxMethod(); // returns a jQuery promise
Q(myAjaxMethod()); // returns a Q promise that wraps the jQuery one
So for example - your ajax method can be:
function ajax(config){
return Q($.ajax(config));
}
Which is shorter and less error prone.
It seems like for the sake of one good idea (wrap jQuery's promises in Q promises) you've come up with two or three bad ideas. That stuff you're doing with dfd.promise.then is complete insanity, and it doesn't work. Observe:
var p = App.ajax({ url: "..." });
p.then(function (data) {
// ok, no problem
console.log(data);
});
// if p is not hopelessly broken at this point, the following statement
// should have exactly the same outcome as the previous one
p.then(function (data) {
// Logs "undefined"! You REPLACED p's .then() method with
// one from an empty promise in the last statement!
console.log(data);
});
Even if you find a way around the particular issue above, it's not wise to do this sort of thing without having a deep understanding of the implications. I have not read the Q library's source in much detail, but it would not surprise me if there are some internal dependencies that rely on the assumption that the then method isn't getting swapped out for something else.
As Benjamin Gruenbaum says, don't overthink it. Don't try to turn promises into something they're not in an attempt to avoid updating your code. Promises/A+ compliant promises only pass one argument into their .then() handlers. By trying to circumvent that, you're completely undermining the good idea you started off with.
The best I can offer is not dramatically different from some of the stuff in the question, however is is more economically mechanised and exploits Q's ability to coerce a jQuery promise, as recommended by Benjamin G.
First a utility function :
function unspread() {
return Array.prototype.slice.call(arguments);
}
Now in your ajax() function you can use the unspread() utility to bundle jQuery's multiple args :
function ajax(options) {
return Q($.ajax(options).then(unspread, unspread));
}
Success and failure handlers unfortunately have to be dissimilar due to the nature of Q's .spread() method, which quite correctly spreads only on success, not on failure.
function foo(options) {
return ajax(options).spread(function(response, textStatus, xhr) {
console.dir(response);
console.log(textStatus);
console.dir(xhr);
return response;//or rebundle the args into an array/object
}, function(arr) {
console.dir(arr[0]);//xhr
console.log(arr[1]);//textStatus
console.dir(arr[2]);//ErrorThrown
throw arr;//or any of its elements
});
}
If you really wanted named error arguments in Q, then here's a decidedly messy (and untested) approach :
function foo(options) {
return ajax(options).spread(function(response, textStatus, xhr) {
console.dir(response);
console.log(textStatus);
console.dir(xhr);
return response;
}, function(err) {
if($.isArray(err)) {
//it's a $.ajax error array
return err; //put the error array on the success path so it can be spread
} else {
throw err;//maybe it's an unbundeled error
}
}).spread(function(xhr, textStatus, ErrorThrown) {
if(arguments.length == 3) {//test because the genuine success path will lead you here too.
console.dir(xhr);
console.log(textStatus);
console.dir(ErrorThrown);
}
});
}
But, even if you could get that to work, it's rather extreme just to obtain named args.
I'm sure a "Q.superSpread()" method could be written to do the job. I gave it 10 minutes and decided it was not trivial, equally extreme and probably conceptually unsound.

Categories