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

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.

Related

jQuery .when not working as expected with rest operator

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.

Returning a Promise, but getting 'Cannot read property 'then' of undefined'

I've read through the other examples of people getting this error on here, but I'm continuing to get the same error when trying to chain promises together.
In the code below, pegasus (the promise-based http library: https://github.com/typicode/pegasus) makes a call to an API, gets back some JSON, and returns a promise. I can process the data it returns back using the first then without an issue. This code is synchronous, and initially I wasn't returning a promise because I was under the impression that it wasn't necessary (and still might not be). In the code below I tried wrapping it in a promise anyway as that was a commonly offered solution for other similar questions on Stack Overflow, but that doesn't seem to have solved it either.
Still, I am getting the TypeError: Cannot read property 'then' of undefined message.
var polygonGeo, centroidGeo;
var hexData = pegasus(hexUrl)
.then(function(data, xhr) {
return new Promise(function(resolve, reject) {
var features = data.features;
var polygons = features.filter(function(feature) {
return feature.properties.kind === 'Hexagon';
});
var centroids = features.filter(function(feature) {
return feature.properties.kind === 'Centroid';
});
polygonGeo = $.extend(true, {}, data);
centroidGeo = $.extend(true, {}, data);
polygonGeo.features = polygons;
centroidGeo.features = centroids;
if (typeof polygonGeo !== 'undefined' &&
typeof centroidGeo !== 'undefined') {
resolve();
}
});
}).then(function() {
console.log('It never reaches here.');
}).catch(function(error) {
console.log(Error(error));
});
Any idea where I might be going wrong?
The problem is that pegasus doesn't implement A+/Promises. Or, really, any kind of promises in the generally-accepted sense.
If we look at its source code, we can see that its then function doesn't return anything:
function pegasus(a, xhr) {
xhr = new XMLHttpRequest();
// ...lines omitted...
xhr.onreadystatechange = xhr.then = function(onSuccess, onError, cb, data) {
// ...lines omitted, there is no `return` anywhere, at all...
};
// Send
xhr.send();
// Return request
return xhr;
}
It's creating an XHR object, then adding a then property to it which is a function which is nothing short of bizarre not a proper then in the Promises sense, and returns that XHR object.
A proper then function returns a new promise. Neither the pegasus function nor its then returns a promise, at all.
#Brideau sorry for that, then may be confusing. As #T.J. explained, it's not a real Promise. I'll emphasise that part in the README.
About not using Promises, the idea of Pegasus is that the faster it loads, the faster it can start making requests.
That's why I'm not using a Promise polyfill. It's to make the library as small as possible. I'm only supporting GET + JSON requests too for the same reason.
Regarding the "weird" code style, I'm using
Byte-saving Techniques. That's why, for example, function parameters are used as variable placeholders.
So Pegasus should be used at the begin, but after that you may use other libraries to make more complex requests.
I hope it makes things a bit clearer :)

How to return Parse promise from cloud code module?

Would anyone know how to return the result of a Promise from a cloud code module? I am using the examples here but it keeps telling me that options is undefined (or nothing if I check with if(options) first.
I am calling the function with module.function as a promise, but still not getting results.
And ideas?
Edit: I can FORCE it to work but calling:
module.function({},{
success:function(res){
//do something
},
error:function(err){
//handle error
}
})
but this isn't really great since 1) I have to stick the empty object in there 2) I can't force the object to work like a promise and therefore lose my ability to chain.
Not sure if the issue is with modules or promises. Here's some code that illustrates both, creating a module with a function that returns a promise, then calling that from a cloud function.
Create a module like this:
// in 'cloud/somemodule.js'
// return a promise to find instances of SomeObject
exports.someFunction = function(someValue) {
var query = new Parse.Query("SomeObject");
query.equalTo("someProperty", someValue);
return query.find();
};
Include the module by requiring it:
// in 'cloud/main.js'
var SomeModule = require('cloud/somemodule.js');
Parse.Cloud.define("useTheModule", function(request, response) {
var value = request.params.value;
// use the module by mentioning it
// the promise returned by someFunction can be chained with .then()
SomeModule.someFunction(value).then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
});

Join thread in JavaScript

Probably asked before, but after the serious searching I'm still not able to find a proper solution. Please consider something like this:
function compute() {
asyncCall(args, function(err, result) {
});
/* 'join thread here' */
}
Even though asyncCall is asynchronous I'd like to use the result and return it from the function compute synchronously. asyncCall is a library call and I can't modify it in any way.
How to wait properly for the asynchronous result without setTimeout and watching a conditional variable? This is possible but suboptimal.
not sure how you can really use something that doesn't exist yet, but it's easy enough to return a slot where the result will be:
function compute() {
var rez=[];
asyncCall(args, function(err, result) {
rez[0]=result;
if(rez.onchange){ rez.onchange(result); }
});
/* 'join thread here' */
return rez;
}
now, you can refer to the [0] property of the return, and once the callback comes in, compute()[0] will have the result. It will also fire an event handler you can attach to the returned array that will fire when the data updates inside the callback.
i would use something more formal like a promise or secondary callback, but that's me...
EDIT: how to integrate a callback upstream:
// sync (old and busted):
function render(){
var myView=compute();
mainDiv.innerHTML=myView;
}
//async using my re-modified compute():
function render(){
var that=compute();
that.onchange=function(e){ mainDiv.innerHTML=e; }
}
see how making it wait only added a single wrapper in the render function?
There's no await syntax in browsers that is widely available. Your options are generally limited to Callback patterns or Promises.
NodeJS follows a callback pattern for most async methods.
function someAsyncMethod(options, callback) {
//callback = function(error, data)
// when there is an error, it is the first parameter, otherwise use null
doSomethingAsync(function(){
callback(null, response);
});
}
....
someAsyncMethod({...}, function(err, data) {
if (err) return alert("OMG! FAilZ!");
// use data
});
Another common implementation is promises, such as jQuery's .ajax() method...
var px = $.ajax({...});
px.data(function(data, xhr, status){
//runs when data returns.
});
px.fail(function(err,xhr, status){
//runs when an error occurs
});
Promises are similar to events...
Of the two methods above, the callback syntax tends to be easier to implement and follow, but can lead to deeply nested callback trees, though you can use utility patterns, methods like async to overcome this.

Chaining Promises recursively

I'm working on a simple Windows 8 app in which I need to fetch a set of data from a web site. I am using WinJS.xhr() to retrieve this data, which returns a Promise. I then pass a callback into this Promise's .then() method, which supplies my callback with the returned value from the asynchronous call. The .then() method returns another Promise, giving it the value that my callback returns. The basic structure of such a query would be as follows:
WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
//do stuff
return some_value;
}).then(
function secondcallback( some_value )
{
//do stuff
});
In my situation, however, I may need to make additional queries for data depending on the data returned by the first query, and possibly more queries depending on THAT data... and so on, recursively.
I need a way to code this such that the final .then() is not executed until ALL the recursions have completed, similar to this:
function recurse() {
return WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
if( result_from_xhr == something )
{
recurse();
}
});
}
recurse().then(
function final()
{
//finishing code
});
The problem is that, of course, the finishing code is called as soon as the first level of recursion completes. I need some way to nest the new promise and the old promise from within the callback.
I hope my question is clear enough, I'm really not sure how to explain it and frankly the idea of asynchronous recursive code makes my head hurt.
What I would do here is to create a whole new, standalone promise that you can complete manually, and return that from the recurse() function. Then, when you hit the point that you know you're done doing async work, complete that promise.
Promise.join works when you've got a known set of promises - you need the entire array of promises available before you call join. If I followed the original question, you have a variable number of promises, with more possibly popping up as part of async work. Join isn't the right tool in these circumstances.
So, what does this look like? Something like this:
function doSomethingAsync() {
return new WinJS.Promise(function (resolve, reject) {
function recurse() {
WinJS.xhr({ url: "http://www.example.com/" })
.then(function onResult(result_from_xhr) {
if (result_from_xhr === something) {
recurse();
} else {
// Done with processing, trigger the final promise
resolve(whateverValue);
},
function onError(err) {
// Fail everything if one of the requests fails, may not be
// the right thing depending on your requirements
reject(err);
});
}
// Kick off the async work
recurse();
});
}
doSomethingAsync().then(
function final()
{
//finishing code
});
I rearranged where the recursion is happening so that we aren't recreating a new promise every time, thus the nested recurse() function instead of having it at the outer level.
I solved this problem in perhaps a different way (I think it's the same problem) while making my own Windows 8 app.
The reason I came across this problem is because, by default, a query to a table will paginate, and only return 50 results at a time, so I created a pattern to get the first 50, and then the next 50, etc, until a response comes back with less than 50 results, and then resolve the promise.
This code isn't going to be real code, just for illustration:
function getAllRows() {
return new WinJS.Promise(function(resolve, reject){
var rows = [];
var recursivelyGetRows = function(skipRows) {
table.skip(skipRows).read()
.then(function(results){
rows = rows.concat(results);
if (results.length < 50) {
resolve(rows);
} else {
recursivelyGetRows(skipRows + 50);
}
})
}
recursivelyGetRows(0);
});
}
so getAllRows() returns a promise that resolves only after we get a result back with less than 50 results (which indicates it's the last page).
Depending on your use case, you'll probably want to throw an error handler in there too.
In case it's unclear, 'table' is a Mobile Services table.
You will need to use Promise.join().done() pattern. Pass an array of Promises to join(), which in your case would be a collection of xhr calls. The join() will only call done() when all of the xhr Promises have completed. You will get an array of results passed to the done(), which you can then iterate over and start again with a new Promise.join().done() call. The thing to be away of, when using this approach, is that if one of the Promises passed to join() fail, the entire operation is treated as an error condition.
Sorry I don't have time right now to try and stub out the code for you. If I get a chance, I will try to later. But you should be able to insert this into your recursive function and get things to work.
Well, I solved my problem; my recursive function was misinterpreting the data and thus never stopped recursing. Thank you for your help, and I'll be sure to watch those screencasts, as I still don't quite fully grasp the Promise chaining structure.

Categories