I am trying to wrap my post/get/put/delete calls so that any time they are called, if they fail they will check for expired token, and try again if that is the reason for failure, otherwise just resolve the response/error. Trying to avoid duplicating code four times, but I'm unsure how to resolve from a non-anonymous callback.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success, factory.fail);
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
//how to resolve parent's promise from from here
}
}
Alternative is to duplicate this 4 times:
.then(function (rsp) {
factory.success(rsp, deferred);
}, function (err) {
factory.fail(err, deferred);
});
One solution might be using bind function.
function sum(a){
return a + this.b;
}
function callFn(cb){
return cb(1);
}
function wrapper(b){
var extra = {b: b};
return callFn(sum.bind(extra));
}
console.log(wrapper(5));
console.log(wrapper(-5));
console.log(wrapper(50));
For your solution check bellow example
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success.bind({deferred: deferred}), factory.fail.bind({deferred: deferred}));
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
this.deferred.resolve(rsp);
//how to resolve parent's promise from from here
}else {
//retry or reject here
}
}
From what I understand, you just want to resolve the deferred object on success and retry on error in case of expired token. Also you probably want to keep a count of number of retries. If so,
Edit - Seems I misunderstood the question. The answer suggested by Atiq should work, or if you are using any functional JS libraries like underscore or Ramdajs, you could use curry function. Using curry function, you can pass some parameters to the function and the function will get executed only after all the parameters are passed. I have modified the code snippet to use curry function from underscorejs.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data,
config).then(_.curry(factory.success(deferred)),
_.curry(factory.fail(deferred));
return deferred.promise;
}
factory.success = function (deferred, rsp) {
if (rsp) {
//handle resp
deferred.resolve(rsp);
}
}
factory.fail = function(deferred, err){
//handle retry
deferred.reject(err);
}
Related
I'm probably missing the point somewhere here so I'm looking for advice.
I have a nodejs server which is listening for client connections and, based on the data received, makes calls to an API.
The very first call to that API gets an ID which needs to be used on subsequent calls to group them together.
Where I'm struggling is that the call to the API is necessarily asynchronous and in the callback I'm assigning the ID to a variable. While that async call is being processed by the API server, more data is coming in from the client and needs more API calls made BUT I can't fire them until I know the results from the first call as the second calls depend on it.
What's the proper way to handle this? I feel like I should be using Q to promise the results of the first API call to the second, but I'm not sure how it should be structured. Or should I just be queueing up the API calls until the first completes? How would I do that?
Example problem code :
var server = net.createServer();
//set up the callback handler
server.on('connection', handleConnection);
handleConnection(conn) {
//do some stuff...
firstAPICall();
conn.on('data', handleData);
}
handleData(data) {
//do some stuff...
otherAPIcall();
}
firstAPICall() {
client.get("http://myAPI/getID", function (data, response) {
conn.myID = data[0].myID;
}
}
}
otherAPICall() {
//How do I make sure I actually have a value
//in conn.myID from the first function???
client.post("http://myAPI/storeData", { data: {myID:conn.myID, data:someData} }, function (data, response) {
//do some stuff...
}
}
}
Yes, you should be using promises for this. Make a promise for the id that is asynchronously resolved from the first call, and then use it in the subsequent calls:
handleConnection(conn) {
//do some stuff...
var idPromise = firstAPICall();
conn.on('data', function handleData(data) {
//do some stuff...
otherAPIcall(idPromise).then(function(result) {
…
});
});
}
firstAPICall() {
return Q.Promise(function(resolve, reject) {
client.get("http://myAPI/getID", function (data, response) {
resolve(data[0].myID);
});
});
}
otherAPICall(idPromise) {
return idPromise.then(function(myID) {
return new Promise(function(resolve, reject) {
client.post("http://myAPI/storeData", {
data: {myID:myID, data:someData}
}, function (data, response) {
//do some stuff...
resolve(…);
});
});
});
}
Probably you should factor out creating a promise for the result of a client.get call in an extra function. Also make sure to handle errors correctly there and call reject with them. If client would use the node callback conventions, Q even has some nice helper functions for that.
Try using promises. Then use 'then' to call the otherAPICall()
I think you can assume they will be sending data immediately after connecting. So you can simplify and just check in otherAPICall if you have an ID, if not, you can just use a callback. Promises or the async/await keywords might make things sort of nicer down the line but aren't required for this.
var server = net.createServer();
//set up the callback handler
server.on('connection', handleConnection);
handleConnection(conn) {
conn.on('data', handleData(connm, data));
}
handleData(conn, data) {
//do some stuff...
otherAPIcall(conn);
}
checkID(conn, cb) {
if (!conn.myID) {
client.get("http://myAPI/getID", function (data, response) {
conn.myID = data[0].myID;
cb();
});
} else {
cb();
}
}
otherAPICall(conn) {
checkID(conn, function() {
client.post("http://myAPI/storeData", { data: {myID:conn.myID, data:someData} }, function (data, response) {
//do some stuff...
});
});
}
promises can chain values and are always resolved after the callback occurs with the returned value,
function async(value) {
var deferred = $q.defer();
var asyncCalculation = value / 2;
deferred.resolve(asyncCalculation);
return deferred.promise;
}
var promise = async(8)
.then(function(x) {
return x+1;
})
.then(function(x) {
return x*2;
})
.then(function(x) {
return x-1;
});
promise.then(function(x) {
console.log(x);
});
This value passes through all the success callbacks and so the value 9 is logged ((8 / 2 + 1) * 2 - 1).
it is a common pattern that we cascade across a list of sources of data with the first success breaking the chain like this:
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
et cetera. if the getDataN() functions are asynchronous, however, it leads us to 'callback hell':
var data;
getData1(function() {
getData2(function () {
getData3(function () { alert('not found'); })
})
});
where the implementations may look something like:
function getData1(callback) {
$.ajax({
url: '/my/url/1/',
success: function(ret) { data = ret },
error: callback
});
}
...with promises I would expect to write something like this:
$.when(getData1())
.then(function (x) { data = x; })
.fail(function () { return getData2(); })
.then(function (x) { data = x; })
.fail(function () { return getData3(); })
.then(function (x) { data = x; });
where the second .then actually refers to the return value of the first .fail, which is itself a promise, and which I understood was chained in as the input to the succeeding chain step.
clearly I'm wrong but what is the correct way to write this?
In most promise libs, you could chain .fail() or .catch() as in #mido22's answer, but jQuery's .fail() doesn't "handle" an error as such. It is guaranteed always to pass on the input promise (with unaltered state), which would not allow the required "break" of the cascade if/when success happens.
The only jQuery Promise method that can return a promise with a different state (or different value/reason) is .then().
Therefore you could write a chain which continues on error by specifying the next step as a then's error handler at each stage.
function getDataUntilAsyncSuccess() {
return $.Deferred().reject()
.then(null, getData1)
.then(null, getData2)
.then(null, getData3);
}
//The nulls ensure that success at any stage will pass straight through to the first non-null success handler.
getDataUntilAsyncSuccess().then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
But in practice, you might more typically create an array of functions or data objects which are invoked in turn with the help of Array method .reduce().
For example :
var fns = [
getData1,
getData2,
getData3,
getData4,
getData5
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, fn) {
return promise.then(null, fn);
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(fns).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
Or, as is probably a better solution here :
var urls = [
'/path/1/',
'/path/2/',
'/path/3/',
'/path/4/',
'/path/5/'
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, url) {
return promise.then(null, function() {
return getData(url);// call a generalised `getData()` function that accepts a URL.
});
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(urls).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
As a beginner, stumbling across the same problem, I just realized how much simpler this has become with async and await:
The synchronous pattern
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
can now easily be applied to asynchronous code:
let data = await getData1();
if (!data) data = await getData2();
if (!data) data = await getData3();
Just remember to add an async to the function that this code is used in.
I have created a function expression and assigned it to scope, the idea being that the function will initiate an $http request, get a property and then return it.
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
$http.post(url)
.success(function (res) {
return res;
});
}
However when I call $scope.getRequestDigest() it simply returns undefined, presumably because the ajax call hasn't completed yet. Is there any way to delay the return until the $http request is complete? I've tried using the .success() promise but that doesn't seem to work.
$http.post returns a promise (see $q). In order to use the result, bind res to $scope.res:
controller:
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
$http.post(url)
.success(function (res) {
$scope.res = res;
});
}
Then, you can use $scope.res (or res in the template) anywhere you'd like.
After the promise chain is resolved (after success), Angular will run a digest cycle and rebind everything on $scope.
Try
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
return $http.post(url);
}
var digestPromise = $scope.getRequestDigest();
digestPromise.then(function(response){
console.log(response.data);
});
This way you are actually returning a promise, which AngularJS implements through the $q service.
If you were to output (console.log(digestPromise)) digestPromise, you will see that you can all sorts of functions on it, like success or complete, for example.
You could use chain promise using .then
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
return $http.post(url) //this will return a promise
.then(function (res) {
return res.data; //on success this will return a data to caller function
});
}
Then the caller function will have call the function and get the data like this
$scope.getRequestDigest().then(function(data){
console.log(data)
//here you can get data returned from `getRequestDigest` method
})
I'm quite new to Angular, but I'm trying to find out some things.
I do have a method which returns a promise:
preloaderServiceObject.Load = function(referencePaths){
var deferred = $q.defer();
$(referencePaths).each(function(index, referencePath) {
var preloadedElement = document.createElement('img');
{
preloadedElement.onload = deferred.resolve;
preloadedElement.src = referencePath;
}
});
return deferred.promise;
}
This is all working fine and doesn't cause the problem.
However, I do have another method which should return a promise inside the completion call of the promise, like so:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
PreloaderService.Load(images);
});
}
The last line PreloaderService.Load(images); does return a promise as defined in the first function in this post.
But, now I want to call the method `OfficeUIRibbonControlServiceObject.Initialize', but how should i change this method so that I can wait for until the loading of the PreloaderService has been completed?
Just changing the method to return that promise will not work, because the returned object will be undefined (since I'm in the then method of the $http.
Kind regards,
Edit: As suggested by Rouby, using a promise:
The initialize function:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
var deferred = $q.defer();
PreloaderService.Load(images).then(function() {
deferred.resolve();
});
return deferred;
});
}
The InitializeService method:
function InitializeService(serviceInstance, configurationFile) {
serviceInstance.Initialize(configurationFile).then(function() {
console.log('This method has been called.');
});
}
The result of this is that I get: Error: serviceInstance.Initialize(...) is undefined
Create a new deferred in .Initialize that gets resolved when the second .Load finishes, you can then return this deferred as normal.
E.g.
PreloaderService.Load(images).then(function(){ newDeferred.resolve(); }, function(){ newDeferred.reject(); });
Better return the promise:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
return $http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
return PreloaderService.Load(images);
});
}
When you now call the OfficeUIRibbonControlServiceObject.Initialize function the result from PreloaderService.Load will be returned.
example:
OfficeUIRibbonControlServiceObject.Initialize(// myConfiguration //).then (
function success (response) {
console.log("promise success", response)
},
function fail (error) {
console.log("promise fail", error) // the result from PreloaderService.Load
}
);
In general: you can return values or promises in the .then function. When you return a promise. The resolve value of that promise will be returned after that promise is resolved
I'm trying to write a small XHR abstraction as well as learn how to create chainable methods, I am nearly there (I think), but am at a loss as to what to do next, I think my setup is wrong.
What I want to do:
$http.get('file.txt')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
What I've got:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
// just a test to call the success message
if (window) {
xhr.success.call(xhr);
}
return xhr;
};
I'm having trouble 'wiring' up the success/error messages, can anybody help point me in the right direction? Thanks in advance.
jsFiddle
Your chaining is OK, but you have a error at this line:
if (window) {
xhr.success.call(xhr); // Uncaught TypeError: undefined is not a function
}
So JavaScript breaks and doesn't return xhr. Delete thoses lines and it will work.
success and error are simply functions that store the passed functions into an internal storage. Once the XHR responds, your code should execute all callbacks accordingly, depending on the response status.
Now what you need is an object instance per request that stores its own set of success and error callbacks. Also, success and error methods should return the same instance to allow chaining.
This should set you to the right track:
(function (window) {
window.$http = {};
// An object constructor for your XHR object
function XHRObject(url,data){
// store the request data
this.url = url;
this.data = data;
// The callback storage
this.callbacks = {};
this.init();
}
// Methods
XHRObject.prototype = {
init : function(){
// Actual call here
// Depending on result, execute callbacks
var callbacksToExecute;
if(readyState === 4 && response.status === 200){
callbacksToExecute = this.callbacks.success;
} else {
callbacksToExecute = this.callbacks.error;
}
callbacksToExecute.forEach(function(callback){
callback.call(null);
});
},
success : function(cb){
// Create a success callback array and store the callback
if(this.callbacks.hasOwnProperty('success') this.callbacks.success = [];
this.callbacks.success.push(cb);
// You also need a flag to tell future callbacks to execute immediately
// if the current object has already responded
return this;
},
...
}
// A call to get basically returns an object
$http.get = function (url, data) {
return new XHRObject(url,data);
};
})(this);
I hope you can make something out of this:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = function(){
return {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
};
return new xhr();
}
$http.get('url','cb','data')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
Edit: I just realized this is basically the same code you wrote, except I'm missing the if(). It seems that test was causing the code to break.