This is working when I run the UI in the browser, but I'm always getting null for the 'd' in my validateAsync method that needs to call the done method to get it back to the save method. I can't figure out how to use the andCallFake (required to spyOn the unique name test), but also get it to return the (jQuery) deferred to call done on.
Hopefully this code will give you enough context to see what I'm trying to accomplish.
validateAsync = function () {
var d,
isValid = true,
isUnique = false;
// validate that name and description are given
if (layout.Name() === '') {
toastr.warning('Layout name is required', 'Layout');
isValid = false;
}
// validate that there are no other layouts of the same type with the same name
d = uiDataService.GetIsLayoutNameUniqueAsync(layout.LayoutId(), layout.Name(), layout.LayoutTypeId())
.done(function (isUniqueResult) {
isUnique = isUniqueResult.toLowerCase() === "true";
if (!isUnique) {
toastr.warning('Layout name ' + layout.Name() + ' must be unique. There is already a layout with this name.', 'Layout');
}
// this is always undefined in my Jasmine tests
d.done(isValid && isUnique);
})
.fail(function (response) {
mstar.AjaxService.CommonFailHandling(response.responseText);
});
return d;
},
save = function () {
validateAsync()
.done(function (isValidResult) {
var isValid = isValidResult.toLowerCase() === "true";
if (!isValid) {
return;
}
// show a toastr notification on fail or success
dataContext.SaveChanges(layout, uiDataService)
.done(function (layoutIdFromSave) {
toastr.success('The layout was saved. Refreshing...');
})
.fail(function () {
toastr.error('There was an error saving the layout.');
})
.always(function () {
// toastr.info('finished');
});
})
.fail(function () {
throw new Error('There was an error validating before save');
});
};
// in uiDataService
getIsLayoutNameUniqueAsync = function (layoutId, layoutName, layoutTypeId) {
return ajaxService.AjaxGetJsonAsync(webServiceUrl + "GetIsLayoutNameUnique?layoutId=" + layoutId + "&layoutName=" + escape(layoutName) + "&layoutTypeId=" + layoutTypeId);
},
// in ajaxService
ajaxGetJsonAsync = function (url, cache) {
return $.ajax({
type: "GET",
url: url,
dataType: "json",
accepts: {
json: "application/json"
},
cache: cache === undefined ? false : cache
});
},
// in a beforeEach
var getIsLayoutNameUniquePromiseSpy = spyOn(mstar.dataService.UiDataService, "GetIsLayoutNameUniqueAsync")
.andCallFake(function () {
spyObj.called = true;
// http://stackoverflow.com/questions/13148356/how-to-properly-unit-test-jquerys-ajax-promises-using-jasmine-and-or-sinon
var d = $.Deferred();
d.resolve('true');
return d.promise();
});
// and a test
it("should show a toastr", function () {
// Act
vm.GetLayout().Name('Test');
vm.GetLayout().Description('Test');
vm.Save();
// Assert
expect(toastr.success).toHaveBeenCalledWith('The layout was saved. Refreshing...');
});
Aligned, I don't know a lot about Jasmine but taking the code on its own merits, it's a lot easier to see what's going on if it's stripped right down to the bare bones.
Greatly simplified, validateAsync() is currently structured as follows :
validateAsync = function () {
...
var d = fn_that_returns_a_promise().done(function() {
...
d.done(boolean);
}).fail(function() {
...
});
return d;
};
which can't be right, because .done() doesn't accept a boolean argument and, whereas I can't say it's definitely wrong, d.done() is not really appropriate inside a d.done() handler (though maybe in different circumstances).
I suggest you want to employ .then() to filter the success case (thus passing on a new promise resolved with your boolean value), while retaining .fail() for the failure case; giving a structure as follows :
validateAsync = function () {
...
return uiDataService.GetIsLayoutNameUniqueAsync(...).then(function(...) {
...
return isValid && isUnique;
}).fail(function(...) {
...
});
};
Thus, save() can be as follows :
save = function() {
validateAsync().done(function(isValid) {
//validation success
if(!isValid) return;
...
}.fail(function() {
//validation failure
...
});
};
Now all you have to do is "join up the dots" (ie. reinsert your own statements etc) and hope I haven't made any mistakes.
Related
Here is some code.
function callServiceSync(url, obj) {
var result = true;
callServiceOptions(url, obj, function(res){
result = res;
alert("RESULT CB: "+JSON.stringify(result));
}, {async: false});
alert("RESULT POST-CB: "+JSON.stringify(result));
return result;
}
When it runs, the alert box says:
RESULT CB: {"success":true,"data":"dtrombley"}
(that's what the webservice returns, in reality), and then:
RESULT POST-CB: true
Why isn't this assignment to the closure variable working? Am I misunderstanding how JS closures work?
callServiceOptions() is rather longwinded - but the gist of it is that it calls jQuery's $.ajax method with it's last arguments options extended into some default (in this case, async is disable for sync query), and then executes the provided callback.
Is $.ajax() maybe executing something in some kind of way that disables/screws up closures (but I call the cb, not $.ajax()!)? If so, how to fix that?
For completeness (though really this function shouldn't be able to screw things up to my thinking):
function callServiceOptions(url, obj, cb, options) {
optSuccess = options.success;
optError = options.error;
opts = {}
$.extend({},options)
if (!opts.contentType) {
opts.contentType = "application/json";
}
if (!opts.dataType) {
opts.dataType = "json";
}
if (!opts.data && obj) {
opts.data = JSON.stringify(obj);
}
if (!opts.processData) {
opts.processData = false;
}
if (!opts.method) {
opts.method = "POST";
}
opts.error = function(jqXHR, textStatus, errorThrown) {
if (optError) {
optError(jqXHR, textStatus, errorThrown);
}
if (jqXHR.responseText) {
responseObj = JSON.parse(jqXHR.responseText)
if (responseObj && responseObj.message)
cb({
success: false,
message: responseObj.message
})
return
}
cb({
success: false,
message: errorThrown
});
};
opts.success = function(data, textStatus, jqXHR) {
if (optSuccess) {
optSuccess(data,textStatus,jqXHR);
}
cb(data);
};
if (url.charAt(0) == '/') {
url = url.substr(1);
}
opts.url = WEBCTX.getBaseURL() + url;
$.ajax(opts);
}
This is not a duplicate of any question asking how to return a value from an async event. I have a working callServiceAsync() which does that beautifully. I am using synchronous mode, if you aren't familiar with it, please take a pass on this question...
Your function is asynchronous.
While you have created an object that looks like {async: false}, you are passing it as the 4th argument to callServiceOptions so it gets placed in the options variable.
You only access that variable twice (options.success and options.error) so the async property is never used for anything (so $.ajax uses the default value of true).
Adding console.log(opts) just before you call $.ajax(opts); will show this.
I have some code that will dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request to a server.
The idea is that:
A server provides a "Scenario" for me to generate an AJAX Request.
I generate an AJAX Request based off the Scenario.
I then repeat this process, over and over in a Loop.
I'm doing this with promises here: http://jsfiddle.net/3Lddzp9j/11/
However, I'm trying to edit the code above so I can handle an array of scenarios from the initial AJAX request.
IE:
{
"base": {
"frequency": "5000"
},
"endpoints": [
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/alvarengarichard",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
},
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/dkang",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
}
]
This seems like it would be straight forward, but the issue seems to be in the "waitForTimeout" function.
I'm unable to figure out how to run multiple promise chains. I have an array of promises in the "deferred" variable, but the chain only continues on the first one--despite being in a for loop.
Could anyone provide insight as to why this is? You can see where this is occuring here: http://jsfiddle.net/3Lddzp9j/10/
The main problems are that :
waitForTimeout isn't passing on all the instructions
even if waitForTimeout was fixed, then callApi isn't written to perform multiple ajax calls.
There's a number of other issues with the code.
you really need some data checking (and associated error handling) to ensure that expected components exist in the data.
mapToInstruction is an unnecessary step - you can map straight from data to ajax options - no need for an intermediate data transform.
waitForTimeout can be greatly simplified to a single promise, resolved by a single timeout.
synchronous functions in a promise chain don't need to return a promise - they can return a result or undefined.
Sticking with jQuery all through, you should end up with something like this :
var App = (function ($) {
// Gets the scenario from the API
// sugar for $.ajax with GET as method - NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario2');
};
var checkData = function (data) {
if(!data.endpoints || !data.endpoints.length) {
return $.Deferred().reject('no endpoints').promise();
}
data.base = data.base || {};
data.base.frequency = data.base.frequency || 1000;//default value
};
var waitForTimeout = function(data) {
return $.Deferred(function(dfrd) {
setTimeout(function() {
dfrd.resolve(data.endpoints);
}, data.base.frequency);
}).promise();
};
var callApi = function(endpoints) {
console.log('Calling API with given instructions ...');
return $.when.apply(null, endpoints.map(ep) {
return $.ajax({
type: ep.method,
dataType: ep.type,
url: ep.endpoint
}).then(null, function(jqXHR, textStatus, errorThrown) {
return textStatus;
});
}).then(function() {
//convert arguments to an array of results
return $.map(arguments, function(arg) {
return arg[0];
});
});
};
var handleResults = function(results) {
// results is an array of data values/objects returned by the ajax calls.
console.log("Handling data ...");
...
};
// The 'run' method
var run = function() {
getScenario()
.then(checkData)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(null, function(reason) {
console.error(reason);
})
.then(run);
};
return {
run : run
}
})(jQuery);
App.run();
This will stop on error but could be easily adapted to continue.
I'll try to answer your question using KrisKowal's q since I'm not very proficient with the promises generated by jQuery.
First of all I'm not sure whether you want to solve the array of promises in series or in parallel, in the solution proposed I resolved all of them in parallel :), to solve them in series I'd use Q's reduce
function getScenario() { ... }
function ajaxRequest(instruction) { ... }
function createPromisifiedInstruction(instruction) {
// delay with frequency, not sure why you want to do this :(
return Q.delay(instruction.frequency)
.then(function () {
return this.ajaxRequest(instruction);
});
}
function run() {
getScenario()
.then(function (data) {
var promises = [];
var instruction;
var i;
for (i = 0; i < data.endpoints.length; i += 1) {
instruction = {
method: data.endpoints[i].method,
type: data.endpoints[i].type,
endpoint: data.endpoints[i].endPoint,
frequency: data.base.frequency
};
promises.push(createPromisifiedInstruction(instruction));
}
// alternative Q.allSettled if all the promises don't need to
// be fulfilled (some of them might be rejected)
return Q.all(promises);
})
.then(function (instructionsResults) {
// instructions results is an array with the result of each
// promisified instruction
})
.then(run)
.done();
}
run();
Ok let me explain the solution above:
first of all assume that getScenario gets you the initial json you start with (actually returns a promise which is resolved with the json)
create the structure of each instruction
promisify each instruction, so that each one is actually a promise whose
resolution value will be the promise returned by ajaxRequest
ajaxRequest returns a promise whose resolution value is the result of the request, which also means that createPromisifiedInstruction resolution value will be the resolution value of ajaxRequest
Return a single promise with Q.all, what it actually does is fulfill itself when all the promises it was built with are resolved :), if one of them fails and you actually need to resolve the promise anyways use Q.allSettled
Do whatever you want with the resolution value of all the previous promises, note that instructionResults is an array holding the resolution value of each promise in the order they were declared
Reference: KrisKowal's Q
Try utilizing deferred.notify within setTimeout and Number(settings.frequency) * (1 + key) as setTimeout duration; msg at deferred.notify logged to console at deferred.progress callback , third function argument within .then following timeout
var App = (function ($) {
var getScenario = function () {
console.log("Getting scenario ...");
return $.get("http://demo3858327.mockable.io/scenario2");
};
var mapToInstruction = function (data) {
var res = $.map(data.endpoints, function(settings, key) {
return {
method:settings.method,
type:settings.type,
endpoint:settings.endPoint,
frequency:data.base.frequency
}
});
console.log("Instructions recieved:", res);
return res
};
var waitForTimeout = function(instruction) {
var res = $.when.apply(instruction,
$.map(instruction, function(settings, key) {
return new $.Deferred(function(dfd) {
setTimeout(function() {
dfd.notify("Waiting for "
+ settings.frequency
+ " ms")
.resolve(settings);
}, Number(settings.frequency) * (1 + key));
}).promise()
})
)
.then(function() {
return this
}, function(err) {
console.log("error", err)
}
, function(msg) {
console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n")
});
return res
};
var callApi = function(instruction) {
console.log("Calling API with given instructions ..."
, instruction);
var res = $.when.apply(instruction,
$.map(instruction, function(request, key) {
return request.then(function(settings) {
return $.ajax({
type: settings.method,
dataType: settings.type,
url: settings.endpoint
});
})
})
)
.then(function(data) {
return $.map(arguments, function(response, key) {
return response[0]
})
})
return res
};
var handleResults = function(data) {
console.log("Handling data ..."
, JSON.stringify(data, null, 4));
return data
};
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};
return {
// This will expose only the run method
// but will keep all other functions private
run : run
}
})($);
// ... And start the app
App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
You have a return statement in the loop in your waitForTimeout function. This means that the function is going to return after the first iteration of the loop, and that is where you are going wrong.
You're also using the deferred antipattern and are using promises in places where you don't need them. You don't need to return a promise from a then handler unless there's something to await.
The key is that you need to map each of your instructions to a promise. Array#map is perfect for this. And please use a proper promise library, not jQuery promises (edit but if you absolutely must use jQuery promises...):
var App = (function ($) {
// Gets the scenario from the API
// NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario');
};
// mapToInstructions is basically unnecessary. each instruction does
// not need its own timeout if they're all the same value, and you're not
// reshaping the original values in any significant way
// This wraps the setTimeout into a promise, again
// so we can chain it
var waitForTimeout = function(data) {
var d = $.Deferred();
setTimeout(function () {
d.resolve(data.endpoints);
}, data.base.frequency);
return d.promise();
};
var callApi = function(instruction) {
return $.ajax({
type: instruction.method,
dataType: instruction.type,
url: instruction.endPoint
});
};
// Final step: call the API from the
// provided instructions
var callApis = function(instructions) {
console.log(instructions);
console.log('Calling API with given instructions ...');
return $.when.apply($, instructions.map(callApi));
};
var handleResults = function() {
var data = Array.prototype.slice(arguments);
console.log("Handling data ...");
};
// The 'run' method
var run = function() {
getScenario()
.then(waitForTimeout)
.then(callApis)
.then(handleResults)
.then(run);
};
return {
run : run
}
})($);
App.run();
I have a function with a return however in the function there is an async request which holds the value that is suppose to be returned by the function. I understand with the nature of async request the function will complete and not return a value while waiting on the async function to complete.
I attempted to use dojo deferred functions to have my function PostInformation() to return a value within the ajax request callback. I am having some issues and i am not sure where my issue is. Under is my code:
Dojo Deferred Function
function PostInformation(){
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
def = dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function(err){
console.log(err);
def.reject(err);
}
});
def.then(function(data){
console.log('In the then function');
//alert('In the def.then and the results is : ' + data);
if(data == true){
return false;
}else{return true;}
},function(err){
return false;
alert('In the def.error and there has been an error ' + err);
});
//return the value of hasErrors here
};
Devdar, you are making heavy wether out of something quite simple. In particular, you don't need to loop through an object to access one of its properties, and the variable hasErrors is not really necessary.
Your code should simplify to something like this :
function PostInformation() {
var $containers = $("#container1, #container2");
var Employee = {
//data
};
return dojo.xhrPost({
url: 'hello',
content: Employee
}).then(function(data) {
data = JSON.parse(data);
var formErrors = data.formErrors;
if(formErrors.errors) {
$containers.each(function(i, c) {
$(c).children().each(function(wid) {
var val = formErrors[wid.id],
myWidget;
if(val) {
myWidget = dijit.byId(wid.id);
myWidget.validator = function() {
this.set("invalidMessage", val);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
//Send an enhanced error object down the "error" route
throw $.extend(formErrors, {
'message': 'PostInformation(): validation failure'
});
}
//Send the data object down the "success" route
return data;
});
};
PostInformation().then(function(data) {
console.log('PostInformation(): everything went OK');
//access/process `data` here if necessary
//and/or just display a nice "success" message to the user
}, function(err) {
console.error(err.message);
});
Barring mistakes on my part, this code should do everything you want and more. As with your own code, it processes the server's JSON response and returns a Promise, but that's where the similarity stops.
In your code, you seek to return a Promise which is eventually resolved with a boolean to indicate whether or not errors were detected. Whilst this will (if correctly written) meet your immediate needs, it is not the best Promise logic.
In my code, the Promise is resolved only if validation succeeds and rejected if validation fails for whatever reason. Not only is this logically correct behaviour for a Promise (success goes down the success route, and errors go down the error route) but as a bonus should (see note below) also allow you to pass more information to whetever function(s) eventually handle errors. I choose to pass the whole formErrors object enhanced with an error message, thus providing a great deal of freedom in the error handler to display/log/etc as much or as little as is appropriate, and with virtually no assumption inside PostInformation() as to what will happen subsequently. You currently believe that you will only read and act on the boolean formErrors.errors but it could be beneficial to pass as much error data as possible thus allowing yourself the freedom to change your mind at a later date without needing to change anything in PostInformation().
In this regard you can think of PostInformation() as an agent of the server-side service; and like that service, it can be written with incomplete knowledge (or maybe no knowledge at all) of how the (promise of) data/errors it delivers will be used by "consumer code".
NOTE: I have to admit that I'm not 100% familiar with Dojo's Promises, so I'm not sure that a JS plain object can be thrown in the way I indicate. I have found evidence but not proof that it can. For that reason, I am cautious above in saying "your code should simplify to something like this" Anyway, that issue aside, the principle of sending success down the success route and errors down the error route should still apply.
I'd suggest this where you create your own Deferred() object, return it from your PostInformation() function and then register .then() handlers on it so you can pick up the resolve or reject on your own Deferred object that happens inside the PostInformation() function.
The way you had it you were creating your own Deferred() object, but then immediately overwriting it with the xhrPost return result which meant def is now something else and you weren't returning your Deferred from PostInformation() so it can be used outside that function to track the progress.
function PostInformation() {
var hasErrors = false;
var containers = [dijit.byId("container1"), dijit.byId("container2")];
var Employee = {
//data
};
var def = new dojo.Deferred();
dojo.xhrPost({
url: 'hello',
content: Employee,
load: function (data) {
formErrors = {
"errors": true,
"fName": "123",
"surname": "456",
"oNames": "789",
"bSurname": "784585"
};
//formErrors = (JSON.parse(data)).formErrors;
$.each(formErrors, function (key, value) {
if (key == 'errors') {
hasErrors = value;
//console.log('hasErrors set to '+value);
}
});
if (hasErrors == true) {
for (var i = 0; i < containers.length; i++) {
var processingContainer = containers[i];
dojo.forEach(processingContainer.getChildren(), function (wid) {
var widgetName = wid.attr('id');
$.each(formErrors, function (key, value) {
if (key == widgetName && value.length > 0) {
var myWidget = dijit.byId(widgetName);
//var wdgName = dijit.byId(widgetName).attr("id");
var myWidgetValue = value;
myWidget.validator = function () {
//console.log('Attribute Name is :' + wdgName + ' Error Value is : ' + myWidgetValue);
//console.log(wdgName + " : "+myWidgetValue);
this.set("invalidMessage", myWidgetValue);
};
myWidget._hasBeenBlurred = true;
myWidget.validate();
}
});
});
}
}
console.log(hasErrors);
def.resolve(hasErrors);
},
error: function (err) {
console.log(err);
def.reject(err);
}
});
return def.promise;
};
PostInformation().then(function (data) {
console.log('In the then function');
// process data value here which will contain the value you resolved with
}, function(err)
// process an error in the ajax result here
});
I think this is more of an issue with design of the function then.
Since the xHR call is asynchronous, the postInformation shouldn't really return anything unless it's the Deferred object itself. An alternative option is to have postInformation do some sort of event publishing (dojo/topic), that other functions will subscribe to and know how to handle said events.
Already had a layer in JS which helps Gets and Posts to the server with the following implementations :
var getJson = function(url, callback, onError) {
$.get(url)
.done(function(data) {
if(callback != null)
callback(data);
})
.fail (function(error) {
if(onError != null)
onError (error);
else
my.notification.notifyError(onErrorMessage);
});
};
var postJSON = function(url, data, callback, onError) {
$.ajax({
url : url ,
type: "POST" ,
contentType : "application/json"
dataType : "json" ,
date : ko.toJSON(data)
})
.done(function(data) {
if(callback ! = null)
callback(data);
})
.fail(function(error) {
if(onError ! = null)
onError (error);
else
my.notification.notifyError(onErrorMessage);
});
};
Using these implementations on DataService layer :
// Get
var find = function(date, onSuccess , onError) {
var url = /* url with the Controller and Action */ + "?queryString = " + data.filter;
getJson(url , onSuccess , onError);
};
// Post
var save = function(date, onSuccess , onError) {
var url = /* url with the Controller and Action */;
postJSON(url, data, onSuccess, onError);
};
However we use webapi, wich in some cases, a request depends the result of another request generating a "Pyramid of Doom ".
For more elegance of code we are implementing the library Q for asynchronous programming.
To follow the pattern shown above using Q promisses was implemented new method of get as show:
var getJsonDefer = function(url, callback, onError) {
return Q.when($.getJSON(url))
.then (function(data) {
if(callback ! = null)
callback(data);
})
.fail (function(error) {
if(onError ! = null)
onError (error);
else
my.notification.notifyError(onErrorMessage);
});
};
I'm trying to use this implementation on DataService layer this way:
// Get
var find = function(date, onSuccess , onError) {
var url = /* url with the Controller and Action */ + "?queryString = " + data.filter;
return getJsonDefer(url, onSuccess, onError);
};
Anyway in my layer viewmodel javascript suppose I need to use 3 finds and one depends on the outcome of the other:
var = dataOne {
filter: " Filter"
};
findOne(dataOne,
function(result) {
return result;
}
function(error) {
throw error;
})
.then(function(args) {
var = datatwo {
filter: args
};
// Second
findTwo(datatwo ,
function(result) {
return result;
}
function(error) {
throw error;
}
);
})
.then(function(args) {
var = dataThree {
filter: args
};
// Third
findThree(dataThree,
function(result) {
return result;
}
function(error) {
throw error;
}
);
}).catch(function(error) {
// Handle any error from all above steps
})
.done();
My problem :
I admit that I am not able to implement the right way, because all my functions inside .then() are coming with undefined args.
I wonder know what is the best practice to meet the scenario propose here.
I think you will find that the appeal of promises is that you can accomplish your goals with much less code that before. There are a few things you’ll need to know about, though. For one, you will not need to pass or receive callbacks and errbacks anymore. You just need to make sure to return results or promises for results in your handlers. That is how the values propagate to the next handler.
This is an untested adaptation of your program that should illustrate the form:
var find = function(data) {
var url = /* url with the Controller and Action */ + "?queryString = " + data.filter;
return Q($.getJson(url));
};
find({filter: "filter"})
.then(function (firstResult) {
return find({filter: firstResult})
.then(function (secondResult) {
return find({filter: secondResult})
.then(function (thirdResult) {
return [firstResult, secondResult, thirdResult];
});
});
})
.fail(notifyError)
.done();
Note that an error in any stage will be handled by the single fail call at the bottom. Regardless of whether you have an error handler at the end, always end a chain with done() so that any errors that happen before, even in your fail handler, show up in your console.
Note that you only need to nest promises if one operation depends on the previous and the handler needs access both the first and second result. If you only need the result of the second operation, you can just chain.
find({filter: "filter"})
.then(function (firstResult) {
return find({filter: firstResult})
})
.then(function (secondResult) {
return find({filter: secondResult})
.then(function (thirdResult) {
return [secondResult, thirdResult];
});
});
.fail(notifyError)
.done();
You can also flatten things with Q.all and promise.spread, but I will leave you to the documentation at this point because, I hope, you get the gist.
I want to have the most clear code into my app. So I decided to separate the xhr call and the parsing from the view.js.
To do so I added :
In View.js
this._pagePromises.push(myapp.Services.Foo.getFoo()
.then(
function success(results) {
var x = results;
},
function error() {
// TODO - handle the error.
}
));
And in Services.js
Foo:
{
getFoo: function () {
WinJS.xhr({ url: "http://sampleurl.com" }).done(
function completed(request) {
//parse request
var obj = myapp.Parser.parse(request);
return obj;
},
function error(request) {
// handle error conditions.
}
);
}
}
But I have this exception :
0x800a138f - JavaScript runtime error: Unable to get property 'then'
of undefined or null reference
What I want there is :
Start the promise in view.js do some stuff and update the view when getFoo() is completed. I'm not doing this the right way but as a C# developper I have some difficulties to understand this pattern.
Edit :
There is my updated code:
getFoo: function () {
var promise = WinJS.xhr({ url: myapp.WebServices.getfooUrl() });
promise.done(
function completed(request) {
var xmlElements = request.responseXML;
var parser = new myapp.Parser.foo();
var items = parser.parse(xmlElements);
return items;
},
function error(request) {
// handle error conditions.
}
);
return promise;
}
It solved my issue about the 'then' but "return promise" is called before the "return items". So my "caller" does only get the promise and not his result.
What did I miss ?
Edit 2 :
There is the correct way to do this :
Foo:
{
getFooAsync: function () {
return WinJS.Promise.wrap(this.getXmlFooAsync().then(
function completed(request) {
var xmlElements = request.responseXML;
var parser = new myapp.Parser.Foo();
var items = parser.parse(xmlElements);
return items;
}
));
},
getXmlFooAsync: function () {
return WinJS.xhr({ url: "http://sampleurl.com" });
}
}
A more compact way of doing this is to have your function return the return value from WinJS.xhr().then(). What this does is return a promise that will be fulfilled with the return value of your inner completed handler:
Foo:
{
getFooAsync: function () {
return WinJS.xhr({ url: "http://sampleurl.com" }).then(
function completed(request) {
var xmlElements = request.responseXML;
var parser = new myapp.Parser.Foo();
var items = parser.parse(xmlElements);
return items;
}
));
},
}
The caller can then use then/done on the promise it gets from getFooAsync, and the result in the completed handler will be items as returned by the completed handler. (You would not use .done inside this function because you're wanting to return a promise.)
This is the specified behavior of then in Promises-A, to allow for chaining. For more on this, see my post on the Windows 8 Developer Blog, http://blogs.msdn.com/b/windowsappdev/archive/2013/06/11/all-about-promises-for-windows-store-apps-written-in-javascript.aspx.