How to wait until 2 $http requests end in angularjs - javascript

I need to process responses of two different $http requests. What is the best way to do so, knowing that I have to wait for answers of both request before to process their results.
I think I must use something like async, promise, await features, but I cannot figure out how to do so.
var app = angular.module('Async', []);
app.controller('async', function($scope, $http, $timeout, $interval) {
$scope.getCamionTypes = function() {
$http.get("../services/getCamionTypes.php")
.then(function mySucces(response) {
$scope.camionTypes = response.data;
}, function myError(response) {
camionTypes = [];
});
} ;
$scope.getParametres = function() {
var b = $http.get("../services/getParametres.php")
.then(function mySucces(response) {
$scope.parametres = response.data;
}, function myError(response) {
$scope.parametres = [];
});
}
//I make here the first call
$scope.getCamionTypes();
//I make here the second call
$scope.getParametres();
//The following instruction must wait for the end of the 2 calls
console.log('Types de camion : ' + $scope.camionTypes + '\n' + 'Parametres : ' + $scope.parametres);
})

check this
let promise1 = $http.get("../services/getParametres.php");
let promise2 = $http.get("../services/getParametres.php");
$q.all([promise1, promise2]).then(result=>{
//console.log('Both promises have resolved', result);
})

Use Promise.all for such use cases. Promise.all takes an array of promises and gets resolved when both the promises are resolved. It will fail if any of the promises fail.
$scope.getCamionTypes = function() {
return $http.get("../services/getCamionTypes.php")
} ;
$scope.getParametres = function() {
return $http.get("../services/getParametres.php")
}
Promise.all([$scope.getCamionTypes(), $scope.getParametres()]).then(resp => {
//resp[0] and resp[1] will contain data from two calls.
//do all your stuff here
}).catch()

Thank you very much Samira and Shubham for your usefull help !
Here the code modified thank to your advises with the less impact on the architecture of my application.
Best regards.
var app = angular.module('Async', []);
app.controller('async', function($scope, $http, $timeout, $interval, $q) {
$scope.getCamionTypes = function() {
let promise = $http.get("https://www.alphox.fr/ModulPierres_dev/services/getCamionTypes.php")
promise.then(function mySucces(response) {
$scope.camionTypes = response.data;
}, function myError(response) {
camionTypes = [];
});
return promise;
} ;
$scope.getParametres = function() {
let promise = $http.get("https://www.alphox.fr/ModulPierres_dev/services/getParametres.php")
promise.then(function mySucces(response) {
$scope.parametres = response.data;
}, function myError(response) {
$scope.parametres = [];
});
return promise;
}
//I make here the first call
let promise1 = $scope.getCamionTypes();
//I make here the second call
let promise2 = $scope.getParametres();
$q.all([promise1, promise2]).then (result=>{
//The following instructions wait for the end of the 2 calls
console.log('Types de camion : ');
console.log($scope.camionTypes);
console.log('Parametres : ');
console.log($scope.parametres);
$scope.both = {camionTypes: $scope.camionTypes, parametres: $scope.parametres}
});
});

Related

Elegant way of executing callback functions with params [duplicate]

wifiservice.js:
angular.module('app.WifiServices', [])
.factory('WifiService', function(){
var unique_array = angular.fromJson('[]');
function win_wifi(e){
alert("Success");
}
function fail_wifi(e){
alert("Error");
}
function connectWifi(wifi_ssid){
WifiWizard.connectNetwork(wifi_ssid, win_wifi, fail_wifi);
}
function listHandler(a){
var network_array = [];
for(var i=0; i<a.length; i++){
network_array.push("SSID: " + a[i].SSID + " Signal: " + a[i].level);
}
unique_array = network_array.filter(function(elem, pos) {
return network_array.indexOf(elem) == pos;
});
// alert("Wifi List Ready!");
}
function getScanResult(){
WifiWizard.getScanResults(listHandler, failNetwork);
}
function successNetwork(e){
window.setTimeout(function(){
getScanResult();
}, 3000);
}
function failNetwork(e){
alert("Network Failure: " + e);
}
window.setTimeout(function(){
WifiWizard.startScan(successNetwork, failNetwork);
}, 1000);
return {
list: function(){
return unique_array;
},
connectionToWifi: function(name){
connectWifi(name);
}
};
});
My whole controller:
app.controller('WifiController', ['$scope', 'WifiService', function($scope, WifiService) {
$scope.wifiList = [];
window.setTimeout(function() {
$scope.wifiList = WifiService.list();
// alert($scope.wifiList);
$scope.$apply();
}, 5000);
$scope.getList = function() {
$scope.wifiList = WifiService.list();
return $scope.wifiList;
}
$scope.connectWifi = function(name) {
WifiService.connectionToWifi(name);
}
$scope.checkin = function() {
$scope.getList()
.then(function(result) {
console.log(result);
});
}
}]);
What I am trying to do is, to call the $scope.getList(), which returns a list of the surrounding wifi SSIDs, then within $scope.checkin() I would like to process those data.
Since scanning needs some time I have to wait the getList function to finish, thats Why I am trying to use .then, but it gives me the error says on the title. Any ideas?
How to create a AngularJS promise from a callback-based API
To create an AngularJS promise from a callback-based API such as WifiWizard.connectNetwork, use $q.defer:
function connectWifi(wifi_ssid) {
var future = $q.defer();
var win_wifi = future.resolve;
var fail_wifi = future.reject;
WifiWizard.connectNetwork(wifi_ssid, win_wifi, fail_wifi);
return future.promise;
};
The above example returns a $q Service promise that either resolves or rejects using the callbacks from the API.
Well, I came up with something different:
var unique_array = [];
$scope.wifiList = [];
$ionicLoading.show({
template: "Scanning surrounding AP's..."
});
window.setTimeout(function() {
$scope.wifiList = WifiService.list();
// alert($scope.wifiList);
while ($scope.wifiList == []) {
console.log('Scanning...');
}
$scope.$apply();
$ionicLoading.hide();
}, 5000);
What I realize is that the scanning starts once I load the view. So, I added an IonicLoader to force the user to wait, and not be able to press any buttons till the scan is finished. So no function shall wait one another. Not quite code-wise correct, but it does what I need.

AngularJS and angular factory

I have asked similar question before, this time I am stuck with recording data to the blockchain using Angular js and Angular Factory. Please see the code below and advise me where I am wrong
app.js
var app = angular.module('application', [])
app.controller('appController',function($scope, appFactory) {
$('success_create').hide()
$scope.recordData = function(){
appFactory.recordData($scope.data, function(data){
$scope.recordData = data
$("success_create").show()
})}}
app.factory('appFactory',function($http) {
var test = []
factory.recordData = function(data, errorCallback) {
test = data.field1+"-"+data.field2
$http.get('/record_data'+data).then(function(output) {
if (output) {
callback(output)
}).catch(function(error) {
errorCallback(error) })}
return factory
There are so many errors in you're code, that I was considering not to awnser.
But as I felt the need to help you, take the code below as a guide.
var app = angular.module('application', [])
app.controller('appController', function($scope, appFactory) {
// Use angular.element instead of the jQuery `$` selector
angular.element('success_create').hide();
$scope.recordData = function()
{
// The factory returns a promise,
// so you can do the same just as you would with $http
appFactory.recordData($scope.data).then(function(response) {
$scope.recordData = response.data;
angular.element("success_create").show()
});
}
});
app.factory('appFactory',function($http) {
// You define the var as array, but you assign a string later on
// So instead of var test = [] use var test = "" or just var test;
var test = ""; // Or var test;
var factory = {
recordData: function (data, errorCallback)
{
test = data.field1 + "-" + data.field2;
var promise = $http.get('/record_data' + data).then(function(output) {
return output.data;
});
// $http returns a promise, return this to your controller
// You can use the data it returns just like you
// would with the $http method
return promise;
}
}
// In your original code, you return the factory. But you never
// Defined the factory.
return factory;
});
Try out these simply tutorials to learn more about controllers, services ** and promises
https://www.w3schools.com/angular/angular_controllers.asp
https://www.w3schools.com/angular/angular_services.asp
https://docs.angularjs.org/api/ng/service/$q
** Confused about Service vs Factory
#Tabz: modified your code.
app.controller(appController,function($scope, appFactory) {
$("success_create").hide();
$scope.recordData = function(){
appFactory.recordData($scope.data, function(data){
$scope.recordData = data
$("success_create").show();
})
}
})
app.factory("appFactory", function($http) {
factory.recordData = function(data, errorCallback)
$http.get('/record_data'+data).then(function(output) {
if (output)
callback(output)
}).catch(function(error) {
errorCallback(error)
})};
return factory

How to abort/cancel promise for first call during 2 consecutive angular service calls?

When 2 consecutive service calls are made through $http angular service on dropdown item selection and assume first call took time to return and second call return before first call resolve then it shows data of first call for second item selection.
So is there any way to abort first promise if service call made again before first call resolves.
For demo purpose I created sample plunkar which has dropdown with few items I added explicit condition where on selection of first item it took little longer time then other items.
So select first item and immediately select second item to reproduce the scenario, check favorite books display on screen.
Any help appreciated!
Service code:
app.factory('bookService', function($http, $q, $timeout) {
return {
getBook: function(id) {
var promise = $q.defer();
var timeoutSpan = id == 1 ? 3000 : 500;
$http.get("favouriteBooks.json").success(function(data) {
$timeout(function() {
promise.resolve(data.filter(function(obj) {
return obj.id == id
}));
}, timeoutSpan);
}).error(function(msg) {
promise.reject(msg);
})
return promise.promise;
}
}
});
I found 2 ways to handle this scenario -
Case 1: Create global $q deffer object at service level and check whether this object has value/or not before making call to $http request. If this deffer object has value then resolved it explicitly. Plunkar code - code snippet
Service Code:
app.factory('bookService', function($http, $q, $timeout, bookConstants) {
var service = {};
service.mypromise = null;
service.getBook = function(id) {
if (service.mypromise) {
service.mypromise.resolve(bookConstants.EXPLICIT_CANCEL);
service.mypromise = null;
}
service.mypromise = $q.defer();
var timeoutSpan = id == 1 ? 3000 : 500;
$http.get("favouriteBooks.json").success(function(data) {
$timeout(function() {
if (service.mypromise) {
service.mypromise.resolve(data.filter(function(obj) {
service.mypromise = null;
return obj.id == id
}))
}
}, timeoutSpan);
}).error(function(msg) {
service.mypromise.reject(msg);
})
return service.mypromise.promise;
}
return service;
});
Case 2: Return $q deffer object as service response and maintain it at controller level. And in case of consecutive service call first check and explicitly resolve first service call then and proceed with other service call.
Plunkar code - code snippet
Sample Code:
$scope.getSelectedValue = function() {
var id = $scope.selitem.id
$scope.cancel();
var bookPromise = bookService.getBook(id);
$scope.requests.push(bookPromise);
bookPromise.promise.then(getBookSuccess)
.catch(errorCallback)
.finally(getBookComplete);
}
function getBookSuccess(favouriteBooks) {
if (favouriteBooks == 'User Cancelled') {
return;
}
var books = '';
angular.forEach(favouriteBooks, function(book) {
books += book.bookName + ' '
});
$scope.selectedvalues = 'Name: ' + $scope.selitem.name +
' Id: ' + $scope.selitem.id + ' Favourite Book(s): ' + books;
}
function errorCallback(errorMsg) {
console.log('Error Message: ' + errorMsg);
}
function getBookComplete() {
console.log('getBook Has Completed!');
}
$scope.cancel = function() {
if ($scope.requests) {
angular.forEach($scope.requests, function(request) {
request.resolve('User Cancelled');
clearRequest(request);
})
}
};
var clearRequest = function(request) {
$scope.requests.splice($scope.requests.indexOf(request), 1);
};
}]);
app.factory('bookService', function($http, $q, $timeout) {
var service = {};
service.getBook = function(id) {
var promise = $q.defer();
var timeoutSpan = id == 1 ? 3000 : 500;
$http.get("favouriteBooks.json").success(function(data) {
$timeout(function() {
promise.resolve(data.filter(function(obj) {
return obj.id == id
}))
}, timeoutSpan);
}).error(function(msg) {
promise.reject(msg);
})
return promise;
}
return service;
});
You can use the following to wait for two or more promises $q.all([promise1,promise2]).then(function(data){....});
To access data of first promise just use data[0] the second promise return value is saved in data[1] and so on...

cannot update variable using AngularJS

I am new to AngularJS and I am trying to send http request using .foreach loop. Here's my code
app.controller('myCtrl', function($scope, $http) {
var rd;
$http.get(furl,config).then(function mySucces(response) {
rd = response.data;
var webcontent = "";
angular.forEach(rd, function(rd1){
$http.get(furl1 + rd1.slug,config).then(function(res){
webcontent += res.data.title;
console.log(webcontent);//console 1
});
});
console.log(webcontent);//console 2
$scope.myWelcome = webcontent;
}, function myError(response) {$scope.myWelcome = response.statusText;});});
I was expected the console 2 will display the combined "res.data.title", however, it only shows the initial value.(which is empty in this case). The console log 1 is showing correctly - list the increasing "webcontent" variable.
Not sure how to keep the "webcontent" (console 2) updated value. Any response will be appreciated! Thanks!
This isn't an angular problem, this is an asynchronous javascript problem. Your code finished before your promise completes. You could use the query library to wait for all the promises to resolve, like so:
app.controller('myCtrl', function($scope, $http, $q) {
var rd;
$http.get(furl, config).then(function mySucces(response) {
rd = response.data;
var webcontent = "";
var promises = [];
angular.forEach(rd, function(rd1) {
promises.push($http.get(furl1 + rd1.slug, config);
});
$q.all(promises).then(function (results) {
angular.forEach(results, function (result) {
webcontent += result.data.title;
}
$scope.myWelcome = webcontent;
});
}, function myError(response) {
$scope.myWelcome = response.statusText;
});
});
You could just remove the webcontent variable entirely and update the $scope.myWelcome variable directly in it's place, it should work then. So:
app.controller('myCtrl', function($scope, $http) {
var rd;
$http.get(furl,config).then(function mySucces(response) {
rd = response.data;
$scope.myWelcome = "";
angular.forEach(rd, function(rd1){
$http.get(furl1 + rd1.slug,config).then(function(res){
$scope.myWelcome += res.data.title;
console.log(webcontent);//console 1
});
});
}, function myError(response) {$scope.myWelcome = response.statusText;});});
Ajax Calls are always async tasks, they are something similar window.setTimeout. It is impossible to write your code task by task. have a look:
console.log(1);
window.setTimeout(console.log.bind(console, 2));
console.log(3);
This happens because async tasks are executed in subsequent event loops (in the future).
Finally, your snippet could be something like that:
$http
.get(furl, config)
.then(function(response) { return response.data; })
.then(function(resources) {
return $q.all(resources.map(function(resource) {
return $http.get(furl1 + resource.slug, config);
}));
})
.then(function(results) {
return results.map(function(result) {
return result.data.title;
}).join('');
})
.catch(function(response) {
return response.statusText;
})
.then(function(greetings) {
$scope.myWelcome = greetings;
})
;

AngularJS service retry when promise is rejected

I'm getting data from an async service inside my controller like this:
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
$scope.getData = function(query) {
return AsyncService.query(query).then(function(response) {
// Got success response, return promise
return response;
}, function(reason) {
// Got error, query again in one second
// ???
});
}
}]);
My questions:
How to query the service again when I get error from service without returning the promise.
Would it be better to do this in my service?
Thanks!
You can retry the request in the service itself, not the controller.
So, AsyncService.query can be something like:
AsyncService.query = function() {
var counter = 0
var queryResults = $q.defer()
function doQuery() {
$http({method: 'GET', url: 'https://example.com'})
.success(function(body) {
queryResults.resolve(body)
})
.error(function() {
if (counter < 3) {
doQuery()
counter++
}
})
}
return queryResults.promise
}
And you can get rid of your error function in the controller:
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
$scope.getData = function(query) {
return AsyncService.query(query).then(function(response) {
// Got success response
return response;
});
}
}
]);
This actually works:
angular.module('retry_request', ['ng'])
.factory('RetryRequest', ['$http', '$q', function($http, $q) {
return function(path) {
var MAX_REQUESTS = 3,
counter = 1,
results = $q.defer();
var request = function() {
$http({method: 'GET', url: path})
.success(function(response) {
results.resolve(response)
})
.error(function() {
if (counter < MAX_REQUESTS) {
request();
counter++;
} else {
results.reject("Could not load after multiple tries");
}
});
};
request();
return results.promise;
}
}]);
Then just an example of using it:
RetryRequest('/api/token').then(function(token) {
// ... do something
});
You have to require it when declaring your module:
angular.module('App', ['retry_request']);
And in you controller:
app.controller('Controller', function($scope, RetryRequest) {
...
});
If someone wants to improve it with some kind of backoff or random timing to retry the request, that will be even better. I wish one day something like that will be in Angular Core
I wrote an implementation with exponential backoff that doesn't use recursion (which would created nested stack frames, correct?) The way it's implemented has the cost of using multiple timers and it always creates all the stack frames for the make_single_xhr_call (even after success, instead of only after failure). I'm not sure if it's worth it (especially if the average case is a success) but it's food for thought.
I was worried about a race condition between calls but if javascript is single-threaded and has no context switches (which would allow one $http.success to be interrupted by another and allow it to execute twice), then we're good here, correct?
Also, I'm very new to angularjs and modern javascript so the conventions may be a little dirty also. Let me know what you think.
var app = angular.module("angular", []);
app.controller("Controller", ["$scope", "$http", "$timeout",
function($scope, $http, $timeout) {
/**
* Tries to make XmlHttpRequest call a few times with exponential backoff.
*
* The way this works is by setting a timeout for all the possible calls
* to make_single_xhr_call instantly (because $http is asynchronous) and
* make_single_xhr_call checks the global state ($scope.xhr_completed) to
* make sure another request was not already successful.
*
* With sleeptime = 0, inc = 1000, the calls will be performed around:
* t = 0
* t = 1000 (+1 second)
* t = 3000 (+2 seconds)
* t = 7000 (+4 seconds)
* t = 15000 (+8 seconds)
*/
$scope.repeatedly_xhr_call_until_success = function() {
var url = "/url/to/data";
$scope.xhr_completed = false
var sleeptime = 0;
var inc = 1000;
for (var i = 0, n = 5 ; i < n ; ++i) {
$timeout(function() {$scope.make_single_xhr_call(url);}, sleeptime);
sleeptime += inc;
inc = (inc << 1); // multiply inc by 2
}
};
/**
* Try to make a single XmlHttpRequest and do something with the data.
*/
$scope.make_single_xhr_call = function(url) {
console.log("Making XHR Request to " + url);
// avoid making the call if it has already been successful
if ($scope.xhr_completed) return;
$http.get(url)
.success(function(data, status, headers) {
// this would be later (after the server responded)-- maybe another
// one of the calls has already completed.
if ($scope.xhr_completed) return;
$scope.xhr_completed = true;
console.log("XHR was successful");
// do something with XHR data
})
.error(function(data, status, headers) {
console.log("XHR failed.");
});
};
}]);
Following this article Promises in AngularJS, Explained as a Cartoon
you need to retry only when the response comes under 5XX category
I have written a service called http which can be called by passing all http configs as
var params = {
method: 'GET',
url: URL,
data: data
}
then call the service method as follows:
<yourDefinedAngularFactory>.http(params, function(err, response) {});
http: function(config, callback) {
function request() {
var counter = 0;
var queryResults = $q.defer();
function doQuery(config) {
$http(config).success(function(response) {
queryResults.resolve(response);
}).error(function(response) {
if (response && response.status >= 500 && counter < 3) {
counter++;
console.log('retrying .....' + counter);
setTimeout(function() {
doQuery(config);
}, 3000 * counter);
} else {
queryResults.reject(response);
}
});
}
doQuery(config);
return queryResults.promise;
}
request(config).then(function(response) {
if (response) {
callback(response.errors, response.data);
} else {
callback({}, {});
}
}, function(response) {
if (response) {
callback(response.errors, response.data);
} else {
callback({}, {});
}
});
}
I ended up doing this a lot so I wrote a library to help address this problem : )
https://www.npmjs.com/package/reattempt-promise-function
In this example you could do something like
myApp.controller('myController', ['$scope', 'AsyncService',
function($scope, AsyncService) {
var dogsQuery = { family: canine };
$scope.online = true;
$scope.getDogs = function() {
return reattempt(AsyncService.query(dogsQuery)).then(function(dogs) {
$scope.online = true;
$scope.dogs = dogs;
}).catch(function() {
$scope.online = false;
});
}
}]);

Categories

Resources