AngularJS from array of objects to array of functions - javascript

I've got an array of angularjs $http config objects:
var steps= [
{url:"http://api.com/test1", method: "GET"},
{url:"http://api.com/test2",method: "POST"},
{url:"http://api.com/test3",method: "GET"},
]
These are all API calls I need to execute in sequence. The number of this calls can vary.
I would like transform each of this object in a function executing the $http call (so then I can use that with map to obtain an array of functions).
Something like:
function transform(conf){
return $http(conf);
}
But this, obviously, executes the $http call.

You can use Array.reduce to chain the promises.
let promise = steps.reduce((promise, step) => promise.then(() => $http(step)), $q());
ES5
let promise = steps.reduce(function(promise, step){
return promise.then(function(){
return $http(step);
});
}, $q());

One option is to use the async/await pattern where you can await in the loop of request which would make them execute in a sequence.
Try it like this
app.controller('yourControllerController',
async function($scope, $http) {
var steps =
[
{ url:"http://api.com/test1", method: "GET" },
{ url:"http://api.com/test2", method: "POST" },
{ url:"http://api.com/test3", method: "GET" },
];
async function processHttpRequests(){
for(const step of steps){
var result = await $http({ method: step.method, url: step.url});
console.log('Url:' step.url + ' Result: ' + result);
}
};
await processHttpRequests();
});

Related

Angular make multiple http request but wait for each one to finish before making a new one

In angular app I have an array of literal objects containing a url property.
I need to make a http request for each of these object, but one after another.
example:
let arr = [
{url: 'http://example.com/url1'},
{url: 'http://example.com/url2'},
{url: 'http://example.com/url3'}
]
(that is an example, they may be more of objects, and I don't know how many)
Now, I want to make a request to first url and when we have a response from it (or error, doesn't matter) THEN I want to make a request to second etc. Any idea how to efficiently implement that?
I don't want to make these request at single time - each one should be made only after previous was successful or failure.
A possible solution would be to use concatMap, toArray and switchMapTo.
So first you have a list of urls:
let arr = [{url: 'http://example.com/url1'},{url: 'http://example.com/url2'}]
Then you transform them to an Observable:
of(arr)
.pipe(
concatMap(r=> http.get(r.url)), //MAKE EACH REQUEST AND WAIT FOR COMPLETION
toArray(), // COMBINE THEM TO ONE ARRAY
switchMapTo(http.get("FINALURL") // MAKE REQUEST AFTER EVERY THING IS FINISHED
)).subscribe()
We can use tricky method for this. You have to create method in the service like below.
// TestService.ts
callUrlAsync(url): any[] {
return this._http.get<any>(url);
}
In the component you have to call this method as follows.
//component.ts
let arr = [{url: 'http://example.com/url1'},{url: 'http://example.com/url2'},
{url:'http://example.com/url3'}]
public i = 0;
//trigger method
testMethod(){
this.callUrl(this.arr[0]);
}
callUrl(url){
this.testService.callUrlAsync(url)
.subscribe(data => {
console.log(data);
if(this.arr.length > this.i){
this.i++;
this.callUrl(this.arr[this.i]);
}
}
}, error => {
this.Error(error);
if(this.arr.length > this.i){
this.i++;
this.callUrl(this.arr[this.i]);
}
}
);
}
You can combine your observables with flatMap. Since you have a list of observables (or a list of urls that you want to transform into observables), you can do this with reduce.
Example:
// just some options to make a simple GET without parsing JSON
// and reading the response status
const httpOptions = {
headers: new HttpHeaders({
'Accept': 'text/html, application/xhtml+xml, */*',
'Content-Type': 'application/x-www-form-urlencoded'
}),
responseType: 'text',
observe: 'response'
};
const urls = [
{ url: 'www.google.com' },
{ url: 'www.stackoverflow.com' },
{ url: 'www.imgur.com' },
{ url: 'www.reddit.com' },
];
const reducer = (cur, acc) => acc.pipe(
flatMap(r => cur)
);
const all$ = urls.map(({url}) =>
// create the get requests
http.get(url, httpOptions).pipe(
// do something with the result
tap(r => console.log(r.url + ': ' + r.status))
))
.reverse()
.reduce(reducer);
all$.subscribe(x => {});
User forkJoin
Fork Join
let arr = [{url: 'http://example.com/url1'},{url: 'http://example.com/url2'},{url:
'http://example.com/url3'}]
forkJoin(arr);
Use concatAll() method from rxJs which collect observables and subscribe to next when previous completes. (Or you can use forkJoin for multiple request at single time.)
forkJoin - This will group all your request and execute one by one.
forkJoin waits for each http request to complete and group’s all the observables returned by each http call into a single observable array and finally return that observable array.
It accepts array as parameter. for example -
let response1 = this.http.get(requestUrl1);
let response2 = this.http.get(requestUrl2);
let response3 = this.http.get(requestUrl3);
return Observable.forkJoin([response1, response2, response3]);
For ref. - https://medium.com/#swarnakishore/performing-multiple-http-requests-in-angular-4-5-with-forkjoin-74f3ac166d61
This operator is best used when you have a group of observables and only care about the final emitted value of each.
In another way you can call each request in previous request's error or success block, which is lengthy process.
Use concatMap, make sure to catchError because you don't want the entire chain to fail if one link produced an error.
StackBlitz
let results$ = from(arr).pipe(
concatMap(({ url }) =>
requestData(url).pipe(catchError(err => of(`error from ${url}`)))
)
);

Create asynchronous waterfall from objects

Say I have an array of objects which have asynchronous methods:
[
{
partOne: function(input) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
}
]
I want to execute partOne of the first object with my input, pass the result (async) to the partTwo callback, then pass the result of partTwo as input to partOne of the next object and so on. The array may be of one or more objects. I'm wondering what the best pattern to execute this kind of code is?
It is somewhat similar to the waterfall method of async.js: https://caolan.github.io/async/docs.html#waterfall, but I wonder how I can do this without a library and possibly with cleaner code?
Not sure if async/await might help here?
Another option without collecting every callback to an array, using async/await:
async function processWaterfallObject (data, input) {
let result = input
for (let entry of data) {
result = await entry.partOne(result)
result = await entry.partTwo(result)
}
return result
}
This assumes that functions in your data array are either async or return a Promise.
async/await is currently supported by every major browser and is available in node since 7.6.0.
Here is a simple function to invoke each asynchronous function in a stack
function drain(stack, initialArg) {
stack.reduce(function (sectArg, section) {
return Object.keys(section).reduce(async function (arg, key) {
return await section[key].call(null,arg)
}, sectArg)
}, initialArg)
}
To use it ensure that each function in you stack returns a value
var stack = [
{
partOne: function(input) {
// Do something async
console.log('section one partOne', input)
return 'section one partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section one partTwo', result)
return 'section one partTwo output'
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
console.log('section two partOne', resultOfPrevious)
return 'section two partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section two partTwo', result)
return 'section two partTwo output'
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
console.log('section three partOne', resultOfPrevious)
return 'section three partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section three partTwo', result)
return 'section three partTwo output'
}
}
]
So that you can invoke the stack like
drain(stack, 'initialArg')
See this jsfiddle: https://jsfiddle.net/kqj0rror/
Assuming your array of objects given in the original question is under a variable called waterfall
let collector = [];
for (waterfallObj of waterfall) {
let tempArr = Object.values(waterfallObj);//get the functions out of the object
for (waterfallFunc of tempArr) {
collector.push(waterfallFunc);
}
}
//now you have your functions in order in collector
function recursiveCallback(i) {
if (i>collector.length-1) {
return;//if there are no more to call then return
}
collector[i]().then(function(){
recursiveCallback(i+1);
});
}
If you want the next function to do something with the previous functions value then simply change the then to then(function(passedValue and then use that passedValue in the recursiveCallback call within it

How to identify results in Promise.all()

I am creating a module that is executing tasks based on a config it receives. These tasks are asynchronous and are returning a promise. Currently there are only two tasks to handle, but if there are more coming up, I will run into a problem of identifying which result of Promise.all() belongs to which task.
Here is a snap of my current code:
let asyncTasks = [];
let result = {};
if (config.task0) {
asyncTasks.push(task0(param));
}
if (config.task1) {
asyncTasks.push(task1(param));
}
Promise.all(asyncTasks)
.then(results => {
// TODO: There has to be a prettier way to do this..
if (config.task0) {
result.task0 = results[0];
result.task1 = config.task1 ? results[1] : {};
} else if (config.task1) {
result.task0 = {};
result.task1 = results[0];
} else {
result.task0 = {};
result.task1 = {};
}
this.sendResult(result)
});
The config looks like this:
const config = {
task0: true,
task1: true
};
As mentioned in the code, there has to be a prettier and more scaleable way to identify which result is coming from which task, but I can't find anything regarding Promise.all() that could help with this.
How do I identify which value belongs to which promise if Promise.all() resolves?
Promise.all resolves with an array of values, where each value's index in the array is the same as the index of the Promise in the original array passed to Promise.all that generated that value.
If you need anything more fancy you'll need to keep track of it yourself or use another library that offers such functionality (like Bluebird).
There's really no need to use anything other than Promise.all. You're experiencing difficulty because the other structure of your program (config, and arbitrary link of config key to function) is pretty messy. You might want to consider restructuring the code altogether
const config = {
task0: true,
task1: true,
task2: false
}
// tasks share same keys as config variables
const tasks = {
task0: function(...) { ... },
task1: function(...) { ... },
task2: function(...) { ... }
}
// tasks to run per config specification
let asyncTasks = Object.keys(config).map(prop =>
config[prop] ? tasks[prop] : Promise.resolve(null))
// normal Promise.all call
// map/reduce results to a single object
Promise.all(asyncTasks)
.then(results => {
return Object.keys(config).reduce((acc, task, i) => {
if (config[task])
return Object.assign(acc, { [prop]: results[i] })
else
return Object.assign(acc, { [prop]: {} })
}, {})
})
// => Promise({
// task0: <task0 result>,
// task1: <task1 result>,
// task2: {}
// })
Note: we can depend on the order of results because we used Object.keys(config) to create the input array of promises and Object.keys(config) again to create the output object.

AngularJS - forEach() with empty array from $resource

I have an array that I get from a service but in the controller I get an empty value in the forEach() function. This is the code.
Controller
Here, both 'products' and 'copyProducts' are empty. I need to work with the 'copyProducts' array into the forEach() function.
app.controller("controllerApp", function($scope, serviceApp){
var products = serviceApp.query();
$scope.copyProducts = products;
angular.forEach($scope.copyProducts, function(value, key){
console.log("object: "+value);
})
});
Service
app.factory("serviceApp", function($resource){
return $resource("products.json", {}, {
getAll: {
method: "GET",
isArray: true
}
})
})
Your code is wrong since .query() is asynchronous so it doesn't finish immediately and the result is not ready on the next line synchronously. So it needs a callback function to trigger once it's done with it's work.
serviceApp.query().$promise.then(function(res) {
$scope.products = res;
$scope.copyProducts = res;
angular.forEach($scope.copyProducts, function(item) {
console.log(item)
})
});
Alternative:
serviceApp.query({}, function(res, headers){
//etc
});
By the way, if you want to use the getAll method you have defined in your resource then you would not be using query()
serviceApp.getAll().$promise.then(function(res){}).....etc

AngularJS passing more than one value to promise's success callback

I have the following service method:
ResourcesService.prototype.list = function ()
{
var deferred = q.defer();
var settings = fetchSettings();
restService.getAll(resourceName, settings)
.then(function (response) {
deferred.resolve(response.data, {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
});
}, function (error) {
deferred.reject(error.statusText);
});
return deferred.promise;
}
As you can see I am passing two values to deferred.resolve, which are response.data and a metadata object.
Up in the call stack I have:
//"scenes" is an object that inherits from ResourcesService
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
.then(function (firstPage, metadata) {
//firstPage is the "response.data" from previous method
//metadata is undefined, but should be an object with all the values from the headers
});
Why is metadata undefined? I debugged ResourcesService and the headers are being read just fine, but the object passed is as argument to deferred.resolve is not being delegated to my callback function.
Does deferred.resolve support only one argument to be passed to the callback? Do I have to put this metadata in the same object along with the response?
You can't pass more then one parameter into then callback, only the one is expected and considered. What you can do however is to resolve your promise with an object. For example:
ResourcesService.prototype.list = function () {
var settings = fetchSettings();
return restService.getAll(resourceName, settings).then(function (response) {
return {
data: response.data,
metadata: {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
}
};
}, function (error) {
throw new Error(error.statusText);
});
}
Note, that I also fixed deferred anti-pattern in your code, you don't need dummy deferred object, because you already have promise you can return.
Then you would use it like this:
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
.then(function (response) {
var firstPage = response.data,
metadata = response.metadata;
});
While #dsfq is right about not resolving with more than one argument, if you're using q, you could also wrap your resolved values in an array and use .spread() instead of .then() to split them across arguments.
Created:
.then(function (response) {
// Resolve with a single array
deferred.resolve([response.data, {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
}]);
}
Consumed:
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
// .spread() instead of .then()
.spread(function (firstPage, metadata) {
// Works as expected
});

Categories