I'd like to wrap $.ajax().done() in a separate class, which includes validation of JSON responses against a schema.
A sample call could look like this:
myService.get("/api/people").done(function(people) {
// at this point I'm certain the data in people is valid
template.render(people);
}).catch(function() {
// this happens if validation of people failed, even if the request itself was successfull
console.log("empty json or validation failed");
});
The success callback function is passed in done(), but should only be executed if a private function (_validate(data, schema)) returns true. A less elegant version could look like this:
myService.get("api/people", successCallback, errorCallback);
I would like to expose the internal Deferred methods of $.ajax() directly. Is this possible?
You don't need to change the Promises. You can use then to layer promises.
function _validate(data, schema) {
return false;
}
var myService = {
get: function (data) {
return $.ajax(data).then(function (reply) {
if (_validate(reply, schema)) {
return reply;
} else {
// works if your library is Promises/A+ compliant (jQuery is not)
throw new Error("data is not valid JSON"); // causes the promise to fail
/*// else do:
var d = new $.Deferred();
d.reject("data is not valid JSON");
return d.promise();*/
}
});
}
}
myService.get("foo").done(function () { /* success */ }).fail(function () { /*failed */ });
Related
I'm trying to have an async authentication handler which can have an implementation with a callback or promises. The scope is to wrap this function in order to always return a promise, so I can use it further callback/promise agnostic.
I would be most grateful if someone could provide some help with these scenarios, one of the examples is real life.
What if the function is something like :
function getData(id, callback) {
var deferred = Q.defer();
apiCall('/path/to/id', function (err, data) {
if (err) deferred.reject(new Error(err));
//do something with data
deferred.resolve(processedData);
}
deferred.promise.nodeify(callback);
return deferred.promise;
}
and I want to use the .fromCallback in this manner
function myProcessedDataFunction(id) {
return Promise.fromCallback(function (callback) {
return getData(id, callback);
}, {multiArgs: true});
}
Will this work ? Will myProcessedDataFunction return a correct promise ?
A real world example is:
I have an authentication handler which might or might not be implemented with a callback function, and at the same time could be implemented using promises; or it might return a true/false value;
function authHandlerImplementation1(username, password) {
return (username === 'validUsername' && password === 'validPassword');
}
function authHandlerImplementation2(username, password, callback) {
apiCall('/path/to/authorization', function (err, result) {
if (err) return callback(err, null);
callback(null, result);
});
}
function authHandlerImplementation3(username, password) {
return new Promise(function (reject, resolve) {
apiCall('/path/to/authorization', function (err, result) {
if (err) return reject(err);
resove(result);
});
});
}
function authHandlerImplementation4(username, password, callback) {
var deferred = Q.defer();
apiCall('/path/to/id', function (err, data) {
if (err) deferred.reject(new Error(err));
//do something with data
deferred.resolve(processedData);
}
deferred.promise.nodeify(callback);
return deferred.promise;
}
I will try a bluebird implementation for 5th one.
function authHandlerImplementation5(username, password, callback) {
return apiCall('/path/to/id', callback).asCallback(callback); // I hope this is the right way to do it (correct me if I'm wrong, please)
}
and my checkAuth function uses the authHandler Implementation and wants to be callback/promise agnostic.
function checkAuth(username, password) {
var self = this;
return Promise.fromCallback(function(callback) {
return self.authHandler(username, password, callback);
}, {multiArgs: true});
}
In case the authHandlerImplementation does not use callbacks (just returns a value) (implementation1), the checkAuth hangs, nothing happens, my tests fail.
Is there any Bluebird method that can wrap any kind of authHandler Implementation into a promise (be it simple return, callback or promise implementation)?
No, Bluebird has no such utility. Supporting both callbacks and promises is nice if you provide them as an API, but not when you need to consume an API - distinguishing whether the method takes callbacks or returns promises or both is not possible from outside, and writing code that figures it out dynamically (on every call) might be possible but awkward.
Bluebird does have a utility that wraps function which might return, throw or return a promise, it's called Promise.method (and there's also Promise.try which immediately invokes it).
I would recommend to force your callers to use promises if they want to pass an asynchronous auth-handler. If they only have a callback-based one, they still can wrap it in Promise.promisify themselves before making it available to you.
I tried some stuff around and I've managed to make it work. I will provide detailed information of the methods I've tried, below. The scenario is for a json-rpc 2.0 server implementation which receives an authHandler function implementation for checking the validity of the provided credentials in a request.
Update:
Using Promise.method on the authHandler var promisifiedAuthHandler = Promise.method(self.authHandler); works for the synchronous implementation of the authHandler.
Fails for the callback implementation.
Using Promise.promisify on the authHandler wrapped in the Promise.method works for the callback implementation var promisifiedAuthHandler = Promise.promisify(Promise.method(self.authHandler));.
Fails for the synchronous implementation.
Providing a callback for the authHandler (even if it does not use it in the implementation) works for all methods. It goes like this (writing for a general case, and this is part of a module written using ES5-Class node module):
function _checkAuth(req) {
var self = this;
var credentials = self._getCredentials(req);
var promisifiedAuthHandler = Promise.method(self.authHandler); // for the sync implementation
switch (authType) {
// general case, let's say, there are several types
// will just write one as an example.
case Authorization.WHATEVERTYPE:
return promisifiedAuthHandler(credentials, function callback(err, result) {
if (err) return Promise.reject(err);
return Promise.resolve(result);
}
}
}
and the server.enableCookie/JWT/BasicAuth handler can be implemented in the three ways mentioned: sync/callback/promise; As follows:
server.enableCookieAuth(function (cookie) {
return (cookie === validCookieValue);
});
server.enableCookieAuth(function (cookie, callback) {
apiCall('path/to/auth', function(err, result) {
// apiCall could have a promise implementation, as well, and could
// be used with .then/.catch, but this is not that important here, since we care
// about the handler implementation)
if (err) return callback(err, null);
callback(null, result); // can be returned
}
});
server.enableCookieAuth(function (cookie) {
// let's write the apiCall with promise handling, since we mentioned it above
return apiCall('path/to/auth').then(function (result) {
return Promise.resolve(result);
}).catch(function (err) {
return Promise.reject(err);
});
});
Now, we can use our _checkAuth function internally using only promises, agnostic of the authHandler function implementation. As in:
handleHttp: function(req, res) {
var self = this;
// ...other processing
self._checkAuth(req).then(function (result) {
// check if the user is authed or not
if (result) {
// further process the request
} else {
// handle unauthorized request
}
}).catch(function (err) {
// handle internal server or api call (or whatever) error
});
}
The trick was to write our callback with a promise implementation. We always provide the authHandler a callback implementation, even if the authHandler does not use it. This way, we always make sure that the auth handler implementation returns a promise if it uses a callback style implementation.
All comments are welcomed and I would like to hear some opinions on this matter!
Thank you for your prompt responses!
Currently I've started developing some Apps for Office 2013. To develop these apps I'm using office.js which is designed to work with Excel worksheets for example.
Most of the APIs are like:
document.getSelectedDataAsync(p1, p2, function(asyncResult)
{
if (asyncResult.status == 'success')
// do something with asyncResult.value
else if (asyncResult.status == 'fail')
// show asyncResult.error as Error
});
I don't like this type of asynchronous programming. Rather I prefer to use promises and write something like:
document.getSelectedDataAsync(p1, p2)
.done(function(result)
{
// do something with result
})
.fail(function(error)
{
// show error message
})
Is there any way to use office.js API using promises like above?
Sure thing - this example is using the bluebird promise library. Basically, we're converting a callback API to promises:
function promisify(fn){ // take a function and return a promise version
return function(){
var args = [].slice.call(arguments);
return new Promise(function(resolve, reject){
args.push(function(asyncResult){
if(asyncResult.status === 'success') resolve(asyncResult.value);
else reject(asyncResult.error);
});
fn.apply(this, args); // call function
}.bind(this)); // fixate `this`
};
}
This would let you do something like:
document.getSelectedDataPromise = promisify(document.getSelectedDataAsync);
document.getSelectedDataPromise(p1, p2).then(function(result){
// do something with result
}).catch(function(err){
// handle error
});
The simplest form of working around this is by replacing the callback with a custom one which resolves a promise. Note that the below implementation uses the ES6 promises which are available in Chrome:
function toPromise () {
var args = Array.prototype.slice.call(arguments);
var self = this;
return new Promise(function (reject, resolve) {
var callback = function () {
if (arguments[0] instanceof Error) {
return reject.apply(null, arguments);
}
resolve.apply(arguments);
};
args.push(callback);
self.apply(self, args);
});
}
Function.prototype.toPromise = toPromise;
document.getSelectedDataAsync.toPromise(p1, p2).then(function () {
//success
}).catch(function () {
//error
});
I have a function which issues two async requests before yielding some data.
The caller of the method does not need to know about its implementation details. All the caller needs is:
The data returned from the second request.
The ability to call abort and not be returned data.
This is complicated by the fact that abort can be called after the first promise is done. The second request is already in-flight, but the caller has yet to receive data. So, the caller assumes it can call abort, but rejecting the first promise will have no effect.
I work around this issue with the following, but it feels pretty hacky. Am I missing something?
var ajaxOptions = {
url: 'https://www.googleapis.com/youtube/v3/search',
data: {
part: 'id',
key: 'AIzaSyDBCJuq0aey3bL3K6C0l4mKzT_y8zy9Msw',
q: 'Hello'
}
};
function search(options) {
var jqXHR = $.ajax(ajaxOptions);
var innerJqXHR = null;
jqXHR.then(function(data, statusText, jqXHR) {
innerJqXHR = $.ajax(ajaxOptions);
innerJqXHR.done(options.done);
innerJqXHR.fail(options.fail);
return innerJqXHR;
}, options.fail);
return {
promise: jqXHR,
innerPromise: innerJqXHR,
fullAbort: function() {
jqXHR.abort();
if (innerJqXHR !== null) {
innerJqXHR.abort();
}
}
}
}
var searchReturn = search({
done: function() {
console.log('inner done');
},
fail: function() {
console.log('inner or outer fail');
}
});
searchReturn.fullAbort();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Since your code looks a little like pseudo-code (unanswered questions in it for me), the best I can offer is a framework of an idea. The general idea is that you return two things from your search function, a promise that is resolved only when both ajax calls are resolved and an abort function that can be called to abort the process.
function search(options) {
var activeAjax = $.ajax(args for first ajax call).then(function(data) {
// second ajax call changes activeAjax so abort will work
// on the active ajax call
activeAjax = $.ajax(args for second ajax call).then(function(data) {
activeAjax = null;
// collect your final data here and return it
// this will be the resolved value of the final promise
return finalData;
});
return activeAjax;
});
return {
promise: activeAjax,
abort: function() {
if (activeAjax) {
activeAjax.abort();
}
}
};
}
// usage:
var searchInProgress = search(...);
searchInProgress.promise.then(function(data) {
// search finished successfully, answer is in data
}, function(err) {
// either one of the promises failed
});
// and, at any time, you can call searchInProgress.abort();
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()
I am having trouble with creating / understanding promises. I understand the advantages and understand how to use them. Creating own promise-functionality is the difficult part. Simply, how do I convert this function to work with promises:
ret.getDataByGame = function (gameID, playerID) {
var cb = new callbackHelper();
models.gameData.find( { }, function (err, found) {
if (err) {
console.log("error in getting gamedata for gameID: "+gameID);
cb.setData(void 0);
} else {
cb.setData(found);
}
});
return cb;
};
function callbackHelper() {
var self = this;
this.data = false;
this.setData = function (data) {
self.data = data;
};
It should not matter what framework or vanilla js you use to show the example to me.
ret.getGameDataByGame = lib.promisify(models.gameData.find);
might suffice. Or use a dedicated node-style callback helper function:
ret.getGameDataByGame = function(gameID, playerID) {
return lib.ninvoke(models.gameData, "find", {…});
};
For the Q library, check the Adapting Node section of its docs.
For creating a promise with the pattern you've used for your callbackHelper thing, your promise library typically offers Deferreds. You would use them like this:
ret.getDataByGame = function (gameID, playerID) {
var def = new lib.Deferred();
models.gameData.find({…}, function (err, found) {
if (err) {
def.reject("error in getting gamedata for gameID: "+gameID);
} else {
def.fulfill(found);
}
});
return def.promise;
};
See also the The Beginning section in the Q docs.
Just to give a second input, I quickly looked at the promise implementation of Q docs, but this is the implementation that I use, which is supported by default, in browsers (except IE). With respect to your posted algorithm:
//define promise structure for callback function of interest
ret.getDataByGame = function(gameID, playerID){
return new Promise(function(resolve,reject)
{
try
{
//do any callback function etc. which you want to do
models.gameData.find({},function(err, found){
if(err)
{
console.log("error in getting gamedata for gameID: "+gameID);
reject(err); //if there is error, save as reject
}
else
resolve(found); //if have solution, save as resolve
}
}
catch(exc)
{reject('Error exc gameData.find: '+exc.message);}
}); //end of Promise
}
And then where you call your class functions etc.:
//where you physically call the function you defined as a promise function
ret.getDataByGame('input1','input2').then(function(output){
alert("woohoo, you are awesome!, output = "+output);
},function(error){
alert("Output error:\r\n"+error);
});
Here is the definition and implementation of promises which I consider as the "standard" thus far, with browser support versions: Promise doc + tutorial. An the cool thing if you do it for massive amounts of data, and they are async, you really optimize your execution time!! such as:
//repeat promise function
function repeatPromise(inputDataArray)
{
for(var i = 0; i < inputDataArray.length; i++)
{
//where you physically call the function you defined as a promise function
ret.getDataByGame(inputDataArray[i].input1,inputDataArray[i].input2).then(function(resolve){
alert("Output is in async, output = "+resolve);
},function(error){
alert("Output error:\r\n"+error);
});
} //end of for loop
} //end of function
Hope this helps :)