So, we currently have something like this at for asynchronous petitions to a backend.
First: a GenericApi for all the aplications
get: function (url) {
return api_bridge ({
execute: function (auth_headers) {
return $q.resolve($http.get(vm.urlBase + url, {headers: auth_headers}));
}
}).then(handlerSuccessResponse, handlerErrorResponse);
}
and then handler methods
//error
function handlerErrorResponse(response) {
return $q.reject(response);
}
//success
function handlerSuccessResponse(response) {
if (response.isAsync) {
return asyncStatus(response.taskId);
} else {
return $q.resolve(response);
}
}
Success got the key here, since if it is an async method, just recursively calls this method:
function asyncStatus(id){
return getAsyncStatus(id).then(function(response){
if (response.progress < 100){
return asyncStatus(id);
} else {
return getAsyncResult(id);
}
});
}
which calls either of those:
getAsyncStatus: function(id){
return get('/async/status/' + id);
},
getAsyncResult: function(id){
return get('/async/result/' + id);
},
(which call the first method of this list again).
And this way we can do a getter in such a way we don't ever care what happens under the hood:
get("/myurl")
.then(successMethod)
.catch(errorMethod);
(This will work same way whether it is asynchronous or not)
But now we would like to upgrade this system to be able to make callbacks everytime an asyncStatus call is made. Ideally something like this:
get("/myurl")
.then(successMethod)
.catch(errorMethod)
.progression(progressMethod);
The obvious way would be to pass a method as an argument through all the chain and then call it in every iteration of getAsyncStatus, but that kind of defeats the purpose of all this which is having a black box under the hood no one needs to worry about, plus changing this would imply changing all the current existing methods.
To make it the closest possible to the example I guess I would have to do something with $q.defer().notify() but I can't seem to grasp the concept right. Doing this doesn't work:
get("/myurl")
.then(successMethod, null, progressMethod)
.catch(errorMethod);
I have no clue how to make it work.
Well, apparently api_bridge method was breaking the promise binding. Found the solution is doing this:
Generic get:
get: function (url) {
var dfr = $q.defer();
dfr.resolve( api_bridge ({
execute: function (auth_headers) {
return $q.resolve($http.get(vm.urlBase + url, {headers: auth_headers}));
}
}));
return dfr.promise.then(handlerSuccessResponse, handlerErrorResponse);
}
Handler methods (no change):
//error
function handlerErrorResponse(response) {
return $q.reject(response);
}
//success
function handlerSuccessResponse(response) {
if (response.isAsync) {
return asyncStatus(response.taskId);
} else {
return $q.resolve(response);
}
}
Recursive async method:
function asyncStatus(id){
var deferred = $q.defer();
var deferred.promise.then(null, null, angular.noop);
var deferred.notify("This is a notification");
deferred.resolve(getAsyncStatus(id).then(function(response){
if (response.progress < 100){
return asyncStatus(id);
} else {
return getAsyncResult(id);
}
}));
return deferred.promise;
}
which calls either of those (no change);
getAsyncStatus: function(id){
return get('/async/status/' + id);
},
getAsyncResult: function(id){
return get('/async/result/' + id);
},
And now we can do:
get("/myurl")
.then(successMethod, null, progressMethod)
.catch(errorMethod);
Callback progressMethod will be called everytime asyncStatus throws a notification.
Related
I need to perform several functions, some of which may include asynchronous callbacks (based on conditions) when a button is clicked.
I have a submit button that I do not want to prevent default behavior on to perform these operations (i.e. e.preventDefault())
Instead, I need to simply return true or false:
$('#submitBtn').click(function(e) {
return mdl.entry(e);
});
var mdl: {
entry: function(evt) {
if (condition) {
return ...
} else {
return anotherFunction();
}
}
}
function anotherFunction() {
// This function could potentially involve one or more
// asynchronous requests
some.func(obj).then(function(result) {
... do stuff
});
}
What's the best way to deal with this? I know I can poll a global variable, or set a variable and trigger a click event:
$('#submitBtn').click(function(e) {
if (!safeToSubmit) {
e.preventDefault();
mdl.entry(e);
// when finished, it sets safeToSubmit to true
// and triggers a click on the button
}
});
I hope my issue makes sense. Above is dummy code. Probably 10 different functions that conditionally make asynchronous calls. Perhaps a best practices question? I can make it work... I just want a better way :)
One way to deal with this is to always return a Promise. For synchronous functions instead of return someValue you return Promise.resolve(someValue)
Then you can deal with the return value the same way for everything with something like:
mdl.entry(e)
.then((ret) => { //do stuff with ret })
EDIT:
I am imagining something like this:
$('#submitBtn').click(function(e) {
mdl.entry(e)
.then((ret) => { /* whatever is supposed to be donw with the return */ })
});
var mdl = {
entry: function(evt) {
if (condition) {
var res = someSynchronousFunction(evy)
return Promise.resolve(res)
} else {
return anotherFunction();
}
}
}
function anotherFunction() {
return someAsyncFunction(obj).then(function(result) {
//... do stuff and return a value
// someAsyncFunction will return a promise that resolves to this value
});
}
I'm trying to add XPATH evaluation into my form. When user fills XPATH, it's evaluated on a server using AJAX and then return true or false.
The problem is that this function seems to return undefined allways. I suppose it's because of asynchronious behaviour of JS so I used $.when but it didn't helped.
function evalXpath(xpath) {
var test = $.post('/api/test-xpath/', {'xpath': xpath});
test.done(function (data) {
console.log('BEFORE RETURN '+Boolean(data['success']));
return Boolean(data['success']);
})
}
$(document).ready(function () {
$('#id_xpath').on('change', function () {
var xpath = $("#id_xpath").val();
$.when(evalXpath(xpath)).done(function (evaluated) {
console.log('RETURNED '+evaluated);
$('#xpath-valid').text(evaluated ? 'VALID' : 'INVALID');
});
});
});
The console output (as you can see, it's still asynchronious):
Do you have any ideas?
You're really close. A couple of things:
You forgot to return the promise out of evalXpath.
To get proper promise value chaining (e.g., in order for your value from the callback inside evalXpath to then become the resolution value of the promise it returns), use then, not done.
Then when using evalXpath, there's no need for $.when.
So:
function evalXpath(xpath) {
var test = $.post('/api/test-xpath/', {'xpath': xpath});
return test.then(function (data) {
// ^^^^^^ ^^^^
console.log('BEFORE RETURN '+Boolean(data['success']));
return Boolean(data['success']);
})
}
// ...
evalXpath(xpath).then(function (evaluated) {
// ^^^^ (this one could be `done`, but let's be consistent)
I know it is a simple, dumb question but it's been two days since I'm stuck on it.
Consider there is a function, managing creation of object type Course from some object type UnsafeCourse, like so:
Class Schedule {
constructor() {
this.courses = [];
}
sectionNeedsCourse(unsafeCourse) {
this.courses.forEach(function (course, index, array) {
if (course.couldBeRepresentedBy(unsafeCourse)) {
return course;
}
}
return new Course(unsafeCourse, [some additional parameters...]);
}
}
As things in node.js works asynchronously, for loop is gone-through.
I tried different approaches with Promises, didn't work.
sectionNeedsCourse(unsafeCourse) { //-> Promise
return new Promise (function (resolve) {
new Promise(function (resolve) {
this.courses.forEach(function (course, index, array) {
if (course.couldBeRepresentedBy(unsafeCourse)) {
resolve(eachCourse);
}
});
resolve(null);
}).then(function (result) {
console.log(result);
if (result != null) {
addCourse(unsafeCourse).then(function (result) {
resolve(result);
});
} else {
resolve(result);
}
});
});
}
Also, is it a good practice to use multiple Promises in same function?
Does it make heavy overhead?
I can't see any async method in your first example. Array.forEach is not a async function.
You just return the course inside of the callback of forEach, but you must return it directly in sectionNeedsCourse:
sectionNeedsCourse(unsafeCourse) {
var course = this.courses.find(function(course){
course.couldBeRepresentedBy(unsafeCourse) ? course : false;
});
return course ? course : new Course(unsafeCourse, []);
}
As you can see, i also use find instead of forEach, because this is the logic you need here.
As requested here the same example a little bit shorter with an Arrow function:
sectionNeedsCourse(unsafeCourse) {
var course = this.courses.find(course => {
course.couldBeRepresentedBy(unsafeCourse) ? course : false;
});
return course ? course : new Course(unsafeCourse, []);
}
i use angular.js in front side.
in my controller.js i defined an init() method that will be called in
init of my controller.
Init method definition:
var init = function () {
$scope.callTeamsService();
if ($scope.teams.length == 0){
....
}else{
...
}
.....
};
in $scope.callTeamsService i filled in $scope.teams variable.
$scope.callTeamsService method definition:
$scope.callTeamsService = function(){
NavService.getTeams(function (response) {
$timeout(function () {
$scope.teams = response;
}
}, 200);
});
};
In my service.js i defined a getTeams method as follow:
service.getEquipes = function (callback) {
$http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams')
.success(function (response) {
callback(response);
});
};
My problem is when $scope.teams.length == 0 condition is reached the
service.getEquipes method in my service.js is not yet called.
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
service/factory
service.getEquipes = function () {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
};
// controller
var promise = NavService.getTeams.then (
function(data) {
//assign to $scope or do logic
},
function(err){
console.log(err)
}
)
How can i modify this code in order to finish the execution of $scope.callTeamsService method before reaching $scope.teams.length == 0 condition.
That's the wrong way round - you need to wait with executing the $scope.teams.length == 0 condition until the $scope.callTeamsService method has finished.
The classical method would be to give the $scope.callTeamsService method a callback parameter and call that in the timeout instead of $scope.teams = response;. Then you can put your condition in the init function in the callback that you pass.
However, you seem to want to use promises. For that, all of your functions (that all are asynchronous) should return a promise:
service.getEquipes = function (callback) {
return $http.get(urlBase+'users/' + $rootScope.globals.currentUser.loggedUser.idUser + '/teams');
}
(that was easy, $http already returns promises)
$scope.callTeamsService = function() {
return NavService.getTeams().then(function(teams) {
return $timeout(function() {
return teams;
}, 200);
});
};
(and $timeout does as well - by invoking then and returning it from the callback you can chain them and get a new promise for both)
function init() {
return $scope.callTeamsService().then(function(teams) {
$scope.teams = teams;
if (teams.length == 0) {
…
} else {
…
}
});
}
I have a function that include a function to an async function, but I need to return the first one when the async call has been resolved. I'm trying using
return JQuery.when()
The following function is a resume.
function getData(x){
if (x = 1){
return JQuery.when(asynFunction().then(function (data){
(...);
return;
});
}
else {
(...)
return;
}
}
The objective is that getData() doesn't return until the async call has finished.
Any idea?
Thanks for your time!
Assuming that asynFunction() returns a Promise, getData might, at its simplest, look like this :
function getData(x) {
if (x = 1) {
return asynFunction();
}
else {
var myValue = 'whatever';
return myValue;
}
}
However it's often better (though not absolutely necessary) to arrange for a function to return a Promise in all circumstances. This guarantees that wherever getData() is called, the result can be handled in an asynchronous manner (ultimately with .then or .done()) even if it is synchronously derived.
function getData(x) {
if (x = 1) {
return asynFunction();
}
else {
...
var myValue = 'whatever';
return jQuery.when(myValue);
}
}