I'm new to Windows Phone development.
Going through the documentation I got to 'Using Promises' and probably missing something.
Trying to implement the code in the tutorial:
How to handle errors with promises (HTML)
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
} else {
}
args.setPromise(WinJS.UI.processAll());
}
WinJS.Utilities.ready(function () {
var input = document.getElementById('inputUrl');
input.addEventListener('change', changeHandler);
}, false);
};
app.oncheckpoint = function (args) {
};
function changeHandler(e) {
var input = e.target;
var resultDiv = document.getElementById("result");
var div2 = document.getElementById("div2");
WinJS.xhr({url: e.target.value})
.then(function (result) {
if (result.status === 200) {
resultDiv.style.backgroundColor = "lightGreen";
resultDiv.innerText = "Success";
}
return result;
})
.then(function (result) {
if (result.status === 200) {
resultDiv.style.backgroundColor = "yellow";
}
},
function (e) {
resultDiv.style.backgroundColor = "red";
if (e.message != undefined) resultDiv.innerText = e.message;
else if (e.statusText != undefined) resultDiv.innerText = e.statusTExt;
else resultDiv.innerText = 'Error';
});
}
app.start();
})();
If I write a simple string and not URL format i'm getting an unhanded exception. Where I thought that all exceptions will be handled by then() second parameter.
Don't really understand the difference between the XHR code above or this alternative:
WinJS.xhr({ url: e.target.value }).then(
function completed(result) {
if (result.status === 200) {
resultDiv.style.backgroundColor = "lightGreen";
resultDiv.innerText = "Success";
}
},
function error(e) {
resultDiv.style.backgroundColor = "red";
if (e.message != undefined) resultDiv.innerText = e.message;
else if (e.statusText != undefined) resultDiv.innerText = e.statusTExt;
else resultDiv.innerText = 'Error';
}
)
At the end of the tutorial there is example for using WinJS.promise.onerror.
I don't understand where and how to implement it.
If someone can set an example i'll appreciate it.
I'm not entirely sure what you mean by #1, but let me answer what I can.
First of all, if you chain promises together with multiple .then().then() calls, you need to make sure that each return value in each completed function is itself a promise. In the case of WinJS.xhr, there is only one promise involved. For this reason, your second code example is the right one, because in the first you're returning a non-promise result and attempting to attach another .then to it, which doesn't work.
Second, for Windows apps the proper practice is to use .done() for the last promise in the chain. When you have a single promise method like WinJS.xhr, use just .done().
The reason for this is that .done makes sure that exceptions that occur earlier in the chain do not get swallowed and thus disappear. It makes sure that all errors anywhere in the chain get routed to the error handler at the end.
What I think is happening with #1 as you describe it, is that if you give WinJS.xhr a non-URL, it'll throw an exception which will put the promise returned by WinJS.xhr into an error status. Your first .then() call looks for an error handler, but not seeing one, and because you're using a second unneeded .then, and aren't using .done at all, that exception doesn't surface in the promise flow and instead gets picked up by the debugger.
The purpose of WinJS.Promise.onerror is to provide a single handler for all exceptions that occur in promises. This is not useful for handling specific error cases like you're doing; it is meant more for implementing a general-purpose logger or such.
PS, I wrote a lot about promises in Appendix A "Demystifying Promises" in my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition.
Related
I have following code snippet.
this.clickButtonText = function (buttonText, attempts, defer) {
var me = this;
if (attempts == null) {
attempts = 3;
}
if (defer == null) {
defer = protractor.promise.defer();
}
browser.driver.findElements(by.tagName('button')).then(function (buttons) {
buttons.forEach(function (button) {
button.getText().then(
function (text) {
console.log('button_loop:' + text);
if (text == buttonText) {
defer.fulfill(button.click());
console.log('RESOLVED!');
return defer.promise;
}
},
function (err) {
console.log("ERROR::" + err);
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1, defer);
} else {
throw err;
}
}
);
});
});
return defer.promise;
};
From time to time my code reaches 'ERROR::StaleElementReferenceError: stale element reference: element is not attached to the page document' line so I need to try again and invoke my function with "attempt - 1" parameter. That is expected behaviour.
But once it reaches "RESOLVED!" line it keeps iterating so I see smth like this:
button_loop:wrong_label_1
button_loop:CORRECT_LABEL
RESOLVED!
button_loop:wrong_label_2
button_loop:wrong_label_3
button_loop:wrong_label_4
The question is: how to break the loop/promise and return from function after console.log('RESOLVED!'); line?
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead.
SOURCE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
Out of curiosity what are you trying to accomplish? To me it seems like you want to click a button based on it's text so you are iterating through all buttons on the page limited by an attempt number until you find a match for the text.
It also looks like you are using protractor on a non-angular page it would be easier if you used browser.ignoreSynchronization = true; in your spec files or the onPrepare block in the conf.js file so you could still leverage the protractor API which has two element locators that easily achieve this.
this.clickButtonText = function(buttonText) {
return element.all(by.cssContainingText('button',buttonText)).get(0).click();
};
OR
this.clickButtonText = function(buttonText) {
return element.all(by.buttonText(buttonText)).get(0).click();
};
If there is another reason for wanting to loop through the buttons I could write up a more complex explanation that uses bluebird to loop through the elements. It is a very useful library for resolving promises.
You are making it harder on yourself by creating an extra deferred object. You can use the promises themselves to retry the action if the click fails.
var clickOrRetry = function(element, attempts) {
attempts = attempts === undefined ? 3 : attempts;
return element.click().then(function() {}, function(err) {
if (attempts > 0) {
return clickOrRetry(element, attempts - 1);
} else {
throw new Error('I failed to click it -- ' + err);
}
});
};
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.forEach(function(button) {
return clickOrRetry(button);
});
});
One approach would be to build (at each "try") a promise chain that continues on failure but skips to the end on success. Such a chain would be of the general form ...
return initialPromise.catch(...).catch(...).catch(...)...;
... and is simple to construct programmatically using the javascript Array method .reduce().
In practice, the code will be made bulky by :
the need to call the async button.getText() then perform the associated test for matching text,
the need to orchestrate 3 tries,
but still not too unwieldy.
As far as I can tell, you want something like this :
this.clickButtonText = function (buttonText, attempts) {
var me = this;
if(attempts === undefined) {
attempts = 3;
}
return browser.driver.findElements(by.tagName('button')).then(function(buttons) {
return buttons.reduce(function(promise, button) {
return promise.catch(function(error) {
return button.getText().then(function(text) {
if(text === buttonText) {
return button.click(); // if/when this happens, the rest of the catch chain (including its terminal catch) will be bypassed, and whatever is returned by `button.click()` will be delivered.
} else {
throw error; //rethrow the "no match" error
}
});
});
}, Promise.reject(new Error('no match'))).catch(function(err) {
if (attempts > 0) {
return me.clickButtonText(buttonText, attempts - 1); // retry
} else {
throw err; //rethrow whatever error brought you to this catch; probably a "no match" but potentially an error thrown by `button.getText()`.
}
});
});
};
Notes :
With this approach, there's no need to pass in a deferred object. In fact, whatever approach you adopt, that's bad practice anyway. Deferreds are seldom necessary, and even more seldom need to be passed around.
I moved the terminal catch(... retry ...) block to be a final catch, after the catch chain built by reduce. That makes more sense than an button.getText().then(onSucccess, onError) structure, which would cause a retry at the first failure to match buttonText; that seems wrong to me.
You could move the terminal catch even further down such that an error thrown by browser.driver.findElements() would be caught (for retry), though that is probably overkill. If browser.driver.findElements() fails once, it will probably fail again.
the "retry" strategy could be alternatively achieved by 3x concatenatation of the catch chain built by the .reduce() process. But you would see a larger memory spike.
I omitted the various console.log()s for clarity but they should be quite simple to reinject.
In Parse Cloud Code there is a response time limit of 15s. We've been experiencing problems on certain requests that depend on external service requests.
If we have 4 promises, promise 1 & 2 create objects but if the request "runs out of time" on promise 3 we need to destroy whatever was created on the process. I'm cascading the error handling in a similar way as the following example:
var obj1, obj2, obj3;
query.find().then(function() {
obj1 = new Parse.Object('Anything');
return obj1.save();
}).then(function() {
obj2 = new Parse.Object('Anything');
return obj2.save();
}).then(function _success() {
obj3 = new Parse.Object('Anything');
return obj3.save();
}).then(function _success() {
response.success();
}, function _error(err) {
var errorPromises = [];
if (obj1 != undefined) errorPromises.push(deleteExternalStuff(obj1.id));
if (obj2 != undefined) errorPromises.push(deleteExternalStuff(obj2.id));
if (obj3 != undefined) errorPromises.push(deleteExternalStuff(obj3.id));
Parse.Promise.when(errorPromises).then(function _success() {
response.error();
}, function _error() {
response.error(err);
});
});
The deleteExternalStuff function makes a get request on one of the object's id and then returns an object.destroy() promise.
My problem is that the get query works but the destroy promises inside the deleteExternalStuff are not deleting the objects from the database. Any suggestions on how to handle this case?
EDIT:
I've tested and whenever a timeout occurs, the error IS INDEED executed but the destroy() is what is not working just right.
EDIT 2: Added a similar structure for the deleteExternalStuff function
function deleteExternalStuff(objectId) {
var query = Parse.Query('Another Object');
query.equalTo('objXXX', objectId);
return query.find().then(function _success(anotherBunchOfObjects) {
var deletePromises = _.map(anotherBunchOfObjects, function(obj) {
return obj.destroy();
});
return Parse.Promise.when(deletePromises);
}, function _error(error) {
console.log(error); // **ERROR LOG**
return Parse.Promise.as();
});
}
EDIT 3:
With further testing I added an error handler in deleteExternalStuff function and printed to log... Apparently the **ERROR LOG** prints the following: {"code":124,"message":"Request timed out"}
This makes me think that Parse doesn't permit the use of chained promises in error handling if you have already reached the timeout limit... :\
Suggestions for alternate solutions are appreciated.
To make sure all objects are deleted before your request is finished, you will have to wait until all promises are resolved:
var promises = [];
if (obj1 != undefined) promises.push(deleteExternalStuff(obj1.id));
if (obj2 != undefined) promises.push(deleteExternalStuff(obj2.id));
if (obj3 != undefined) promises.push(deleteExternalStuff(obj3.id));
Promise.all(promises).then(function() {
response.error(err);
});
I am putting catches at the end, but they are returning empty object in one particular instance at least. Is a catch necessary for anything unbeknownst, or is it just screwing me up?
$( document).ready(function(){
app.callAPI()//a chainable a RSVP wrapper around a jquery call, with its own success() fail() passing forward to the wrapper, so it will either be a resolved or rejected thenable to which is now going to be chained
.then(
function(env) {
//set the property you needed now
app.someSpecialEnvObj = env;
},
function(rejectMessage){
console.log('call.API() cant set some special env object..');
console.log(rejectMessage);
}
)
.catch(
function(rejectMessage){
if(rejectMessage){
//a just incase, DOES IT HAVE VALUE, for somebody that may have not done their homework in the parent calls?
console.log('you have some kind of legitimate error, maybe even in callAPI() that is not part of any problems inside them. you may have forgotton handle something at an early state, your so lucky this is here!)
} else {
console.log('can this, and or will this ever run. i.e., is there any value to it, when the necessity to already be logging is being handled in each and every then already, guaranteeing that we WONT be missing ANYTHING')
}
}
);
});
Is it wrong? or is there some kind of use for it, even when I still use an error/reject handler on all usages of .then(resolve, reject) methods in all parent chained then-ables?
EDIT: Better code example, I hope. I think I might be still be using some kind of anti-pattern in the naming, I rejectMessage in my e.g., it's the jqXhr object right?
So maybe I should be naming them exactly that or what? i.e. jqXhr? By the way, the reason I like to reject it on the spot inside each then(), if there was an error, is because this way I can copiously log each individual call, if there was a problem specifically there, that way I don't have to track anything down. Micro-logging, because I can.
Promises are helping opening up the world of debugging this way.
Here's the three examples I have tried. I prefer method1, and method2, and by no means am I going back to method3, which is where I started off in the promise land.
//method 1
app.rsvpAjax = function (){
var async,
promise = new window.RSVP.Promise(function(resolve, reject){
async = $.extend( true, {},app.ajax, {
success: function(returnData) {
resolve(returnData);
},
error: function(jqXhr, textStatus, errorThrown){
console.log('async error');
console.log({jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown});
reject({ jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown}); //source of promise catch data believe
}
});
$.ajax(async); //make the call using jquery's ajax, passing it our reconstructed object, each and every time
});
return promise;
};
app.callAPI = function () {
var promise =app.rsvpAjax();
if ( !app.ajax.url ) {
console.log("need ajax url");
promise.reject(); //throw (reject now)
}
return promise;
};
//method 2
app.ajaxPromise = function(){
var promise, url = app.ajax.url;
var coreObj = { //our XMLHttpRequestwrapper object
ajax : function (method, url, args) { // Method that performs the ajax request
promise = window.RSVP.Promise( function (resolve, reject) { // Creating a promise
var client = new XMLHttpRequest(), // Instantiates the XMLHttpRequest
uri = url;
uri = url;
if (args && (method === 'POST' || method === 'PUT')) {
uri += '?';
var argcount = 0;
for (var key in args) {
if (args.hasOwnProperty(key)) {
if (argcount++) {
uri += '&';
}
uri += encodeURIComponent(key) + '=' + encodeURIComponent(args[key]);
}
}
}
client.open(method, uri);
client.send();
client.onload = function () {
if (this.status == 200) {
resolve(this.response); // Performs the function "resolve" when this.status is equal to 200
}
else {
reject(this.statusText); // Performs the function "reject" when this.status is different than 200
}
};
client.onerror = function () {
reject(this.statusText);
};
});
return promise; // Return the promise
}
};
// Adapter pattern
return {
'get' : function(args) {
return coreObj.ajax('GET', url, args);
},
'post' : function(args) {
return coreObj.ajax('POST', url, args);
},
'put' : function(args) {
return coreObj.ajax('PUT', url, args);
},
'delete' : function(args) {
return coreObj.ajax('DELETE', url, args);
}
};
};
app.callAPI = function () {
var async, callback;
async =app.ajaxPromise() ; //app.ajaxPromise() is what creates the RSVP PROMISE HERE<
if(app.ajax.type === 'GET'){async = async.get();}
else if(app.ajax.type === 'POST') {async = async.post();}
else if(app.ajax.type === 'PUT'){async = async.put();}
else if(app.ajax.type === 'DELETE'){ async = async.delete();}
callback = {
success: function (data) {
return JSON.parse(data);
},
error: function (reason) {
console.log('something went wrong here');
console.log(reason);
}
};
async = async.then(callback.success)
.catch(callback.error);
return async;
};
//method 3 using old school jquery deferreds
app.callAPI = function () {
//use $.Deferred instead of RSVP
async = $.ajax( app.ajax) //run the ajax call now
.then(
function (asyncData) { //call has been completed, do something now
return asyncData; //modify data if needed, then return, sweet success
},
function(rejectMessage) { //call failed miserably, log this thing
console.log('Unexpected error inside the callApi. There was a fail in the $.Deferred ajax call');
return rejectMessage;
}
);
return async;
};
I also run this somewhere onready as another backup.
window.RSVP.on('error', function(error) {
window.console.assert(false, error);
var response;
if(error.jqXhr){
response = error.jqXhr;
} else {
//response = error;
response = 'is this working yet?';
}
console.log('rsvp_on_error_report')
console.log(response);
});
Edit error examples
//one weird error I can't understand, an empty string("")?
{
"jqXhr": {
"responseText": {
"readyState": 0,
"responseText": "",
"status": 0,
"statusText": "error"
},
"statusText": "error",
"status": 0
},
"textStatus": "error",
"errorThrown": "\"\""
}
//another wierd one, but this one comes from a different stream, the RSVP.on('error') function
{
"readyState": 0,
"responseText": "",
"status": 0,
"statusText": "error"
}
I am putting catches at the end
That's the typical position for them - you handle all errors that were occurring somewhere in the chain. It's important not to forget to handle errors at all, and having a catch-all in the end is the recommended practise.
even if I use onreject handlers in all .then(…) calls?
That's a bit odd. Usually all errors are handled in a central location (the catch in the end), but of course if you want you can handle them anywhere and then continue with the chain.
Just make sure to understand the difference between an onreject handler in a then and in a catch, and you can use them freely. Still, the catch in the end is recommended to catch errors in the then callbacks themselves.
they are returning empty object in one particular instance atleast.
Then the promise screwed up - it should never reject without a reason. Seems to be caused be the
if ( !app.ajax.url ) {
console.log("need ajax url");
promise.reject();
}
in your code that should have been a
if (!app.ajax.url)
return Promise.reject("need ajax url");
Is a catch necessary for anything unbeknownst?
Not really. The problem is that catch is usually a catch-all, even catching unexpected exceptions. So if you can distinguish them, what would you do with the unexpected ones?
Usually you'd set up some kind of global unhandled rejection handler for those, so that you do not have to make sure to handle them manually at the end of every promise chain.
I think the general question deserves a simple answer without the example.
The pedantic technical answer is 'no', because .catch(e) is equivalent to .then(null, e).
However (and this is a big "however"), unless you actually pass in null, you'll be passing in something that can fail when it runs (like a coding error), and you'll need a subsequent catch to catch that since the sibling rejection handler by design wont catch it:
.then(onSuccess, onFailure); // onFailure wont catch onSuccess failing!!
If this is the tail of a chain, then (even coding) errors in onSuccess are swallowed up forever. So don't do that.
So the real answer is yes, unless you're returning the chain, in which case no.
All chains should be terminated, but if your code is only a part of a larger chain that the caller will add to after your call returns, then it is up to the caller to terminate it properly (unless your function is designed to never fail).
The rule I follow is: All chains must either be returned or terminated (with a catch).
If I remember correctly, catch will fire when your promise is rejected. Since you have the fail callback attached, your catch will not fire unless you call reject function in either your fail or success callback.
In other words, catch block is catching rejection in your then method.
I've got two pieces of code which may or may not run when my app starts. Both produce a messageDialog, so the second must wait on the first. I'm trying to use promises to do this but I'm having issues with the returned value. Can someone point me in the right direction?
WinJS.Promise.as()
.then(function () {
// readTempFile returns a promise, see code below
if (localSettings.values["lastContent"] != 0) {
return readTempFile();
}else{
// what am I supposed to return here? false?
}
})
.then(function () {
// check for release notes
if (localSettings.values["release"] == null) {
var updates = "Updates in this version";
var msg = new Windows.UI.Popups.MessageDialog(updates, "Updates");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
}
});
function readTempFile(){
return new WinJS.Promise(function (complete, error, progress) {
// is my try / catch block redundant here?
try {
tempFolder.getFileAsync("tempFile.txt")
.then(function (file) {
file.openReadAsync().done(function (stream) {
// do stuff with the file
});
var msg = new Windows.UI.Popups.MessageDialog("unsaved work", "Warning");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
complete();
}, function () {
// file not found
error();
});
}
catch (e) {
logError(e);
error();
}
});
}
If both conditions are true, I get an access denied error. As I understand it, readTempFile() returns a promise object which my first then() statement should accept. But I'm not returning anything if the first conditional is met. I don't think that matters in this case as it just falls through to the next then, but it's not good programming.
EDIT:
Amended the readTempFile function to show that it produces a MessageDialog.
Well, let's try an analogy:
function fetchIfNotCached(){
if(!cached){
doSomething();
}
// nothing here
}
This is exactly like your case, only asynchronous. Because it utilizes promises you hook on the return value so:
what am I supposed to return here? false?
Anything you want, I'd personally probably just omit it and refactor it to if(!cached) return doSomething() in the .then. Promises chain and compose and you do not need to create them from callback interfaces unless there is a really good reason to do so.
As for readTempFile you're doing a lot of excess work as it looks like getFileAsync already returns a promise. This is a variation of the deferred anti pattern and can be rewritten as:
function(readTempFile){
return tempFolder.getFileAsync("tempFile.txt").then(function (file) {
return file.openReadAsync();
}).then(function (stream) {
// do stuff with the file, note the return as we wait for it
});
}
Found the answer. Inside the readTempFile function, I needed to add a done() to the showAsync() of my messageDialog, and put the complete() call in there. Otherwise, complete() was returning the promise while the dialog was still up.
function readTempFile(){
return new WinJS.Promise(function (complete, error, progress) {
// is my try / catch block redundant here?
try {
tempFolder.getFileAsync("tempFile.txt")
.then(function (file) {
file.openReadAsync().done(function (stream) {
// do stuff with the file
});
var msg = new Windows.UI.Popups.MessageDialog("unsaved work", "Warning");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync().done(function(){
// must be inside done(), or it will return prematurely
complete();
});
}, function () {
// file not found
error();
// have to add a complete in here too, or the app will
// hang when there's no file
complete();
});
}
catch (e) {
logError(e);
error();
}
});
}
After a couple of experiments, I figured that out myself.
As to what needed to return in my empty else statement, it was another WinJS.Promise.as()
WinJS.Promise.as()
.then(function () {
// readTempFile returns a promise, see code below
if (localSettings.values["lastContent"] != 0) {
return readTempFile();
}else{
return WinJS.Promise.as()
}
})
.then(function () {
// check for release notes
if (localSettings.values["release"] == null) {
var updates = "Updates in this version";
var msg = new Windows.UI.Popups.MessageDialog(updates, "Updates");
msg.commands.append(new Windows.UI.Popups.UICommand("OK", null, 0));
msg.showAsync();
}
});
I found the answer in a Google Books preview of Beginning Windows Store Application Development – HTML and JavaScript Edition By Scott Isaacs, Kyle Burns in which they showed an otherwise empty else statement returning a WinJS.Promise.as()
As a novice in Javascript, I'm confused on which could be the best way to differentiate between the result computed by an asynchronous function, and any exception/error.
If I'm right, you cannot use try-catch in this scenario, as the called function
ends before the callback, and it is this latter who actually may throw an exception.
Well.
I've seen so far some library functions expecting a callback like: function(err, result).
So, one have to test err before using result.
Also I tried myself to return either the actual result or an Error object.
Here, the callback is of the form function(result)
and you have to test result instanceof Error before using it.
It follows an example of this:
function myAsyncFunction ( callBack ) {
async_library_function( "some data", function (err, result) {
if (err) { callBack ( new Error ("my function failed") ); return; }
callBack ( some calculation with result );
});
} // myFunction ()
//
// calling myFunction
//
myAsyncFunction ( function (result) {
if (result instanceof Error ) { log ("some error occurred"); return; }
log ("Alright, this is the result: " + result);
});
What is the best (maybe the common) way to do this?
Thanks.
There are three main approaches that I've been using myself:
Having an "error" parameter passed to the callback.
Having an "error" callback. This is usually combined with (1).
Having some sort of global exception manager.
I'll start with the third one. The idea is to have an object that will allow dispatching errors as well as catching them globally. Something like this:
var ErrorManager = {
subscribers: [],
subscribe: function (callback) {
this.subscribers.push(callback);
},
dispatchError: function (error) {
this.subscribers.forEach(function (s) {
s.apply(window, [ error ]);
});
}
}
This is quite specific to a given situation because there's basically no easy way of tracking the origin of an error as well as it's easy to mess up with this. For example, if you need to hide a dialog box whose contents failed to load, you'd have to propagate this information (e.g. dialog box Id/element) to all the subscribers.
The above approach is good when you want to execute an action that doesn't alter (or alters an independent part) of the web application (e.g. displays a status bar or a message to a console).
The second approach basically makes a separation between successful call and a failure. For example:
$.ajax('/articles', {
success: function () { /* All is good - populating article list */ },
error: function () { /* An error occured */ }
});
In the above example, the success callback is never executed in case of a failure so if you want to have some default behavior to always trigger, you'd need to either sync between the two callbacks or have a callback that is always called (for the above example - complete).
I personally prefer the first approach - having a callback where you have an error object passed along with potential result. This eliminates problems with having to "track" the origin/context on an error as well as worrying about the clean-up procedure (default behavior). So, something like you provided:
function beginFetchArticles(onComplete) {
$.ajax('/articles', {
complete: function (xhr) {
onComplete(xhr.status != 200 ? xhr.status.toString() : null,
$.parseJSON(xhr.responseText)); /* Something a bit more secure, probably */
}
});
}
Hope this helps.
It depends vastly on your implementation. Is this a recoverable error? If it isn't, then the way you are suggesting should work just fine. If it is recoverable then you shouldn't be returning an error. You should be returning an "empty" result. Keep in mind maintainability as well. Do you want instanceof checks throughout the code? Also, I know some programmers like that JavaScript is loose with types, but you run into consistency issues when the expected object passed through can actually be unexpected. Is it a result, or an error, or even something else altogether?
That's one way to do it. Though I'd usually leave any manipulations/processing of the result to the callback function.
Another way is you can pass back both the error and result values to the callback:
callback (err, result); \\ if no error, err = null, if no result, result = null
Alternatively, you can ask for separate error and success callbacks:
function myAsyncFunction ( successCallBack, errorCallBack ) {
\* ... *\
}
And then trigger the appropriate function depending on the received response.
One approach can be like this:
function Exception(errorMessage)
{
this.ErrorMessage = errorMessage;
this.GetMessage = function()
{
return this.ErrorMessage;
}
}
function ResultModel(value, exception)
{
exception = typeof exception == "undefined"? null, exception;
this.Value = value;
this.Exception = exception;
this.GetResult = function()
{
if(exception === null)
{
return this.Value;
}
else
{
throw this.Exception;
}
}
};
And in your usage:
function myAsyncFunction ( callBack ) {
var result;
async_library_function( "some data", function (err, result) {
if (err)
{
result = new ResultModel(null, new Exception("my function failed"));
}
else
{
result = new ResultModel(some calculation with result);
}
callBack ( result );
});
}
myAsyncFunction ( function (result) {
try
{
log ("Alright, this is the result: " + result.GetResult());
}
catch(ex)
{
log ("some error occurred" + ex.GetMessage());
return;
}
});
If you want to make robust programs, you should use promises. Otherwise you have to handle 2 different kinds of errors which is pretty crazy.
Consider how to read a file as JSON without crashing the server:
var fs = require("fs");
fs.readFile("myfile.json", function(err, contents) {
if( err ) {
console.error("Cannot read file");
}
else {
try {
var result = JSON.parse(contents);
console.log(result); //Or continue callback hell here
}
catch(e) {
console.error("Invalid json");
}
}
});
With promises e.g:
var Promise = require("bluebird");
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.json").then(JSON.parse).then(function(result){
console.log(result);
}).catch(SyntaxError, function(e){
console.error("Invalid json");
}).catch(function(e){
console.error("Cannot read file");
});
Notice also how the code grows vertically like with synchronous code instead of horizontally.