I want to call an async function for each item in an observable. As answered here, the solution is to use SelectMany. However, if the async method throws, the subscription will terminate. I have the following solution, which seems to work:
obs.SelectMany(x => Observable
.FromAsync(() => RunAsync())
.Catch(Observable.Empty<string>()));
Is there a more idiomatic solution?
There is a standard way to be able to observe the exceptions that occur in your RunAsync call, and that's using .Materialize().
The .Materialize() method turns an IObservable<T> sequence into a IObservable<Notification<T>> sequence where you can reason against the OnNext, OnError, and OnCompleted calls.
I wrote this query:
var obs = Observable.Range(0, 10);
obs
.SelectMany(x =>
Observable
.FromAsync(() => RunAsync())
.Materialize())
.Where(x => x.Kind != NotificationKind.OnCompleted)
.Select(x => x.HasValue ? x.Value : (x.Exception.Message + "!"))
.Subscribe(x => x.Dump());
With this supporting code:
private int counter = 0;
private Random rnd = new Random();
private System.Threading.Tasks.Task<string> RunAsync()
{
return System.Threading.Tasks.Task.Factory.StartNew(() =>
{
System.Threading.Interlocked.Increment(ref counter);
if (rnd.NextDouble() < 0.3)
{
throw new Exception(counter.ToString());
}
return counter.ToString();
});
}
When I run it I get this kind of output:
2
4
5
1!
6
7
3!
10
8!
9
Each of the lines ending in ! are calls to RunAsync that resulted in an exception.
You can also use OnErrorResumeNext.
obs.SelectMany(x => Observable
.FromAsync(() => RunAsync())
.OnErrorResumeNext(Observable.Empty<string>()));
Related
I am currently trying to return a JSON object array that requires me to do one asynchronous function and then four nested asynchronous map functions in order to populate an array of entities. Basically, each user has an array of orders, each order has an array of items, each item has an array of options and each option has an array of values. I am using loopback4 framework and therefore cannot do res.send once all things have been populated. The function seems to return on the first await, but any await after that, it does not wait on it and instead runs to the end of the function. I have tried using Promises and .thens(), but cannot seem to figure out how to populate each entity fully nested, and then return the array of populated entities. I keep getting an empty array. Below is only one nest of maps, but I cannot get it to even populate up to the first nest and return this, so I decided not to go any further. This is the code:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(async orders => {
orders.map(async (val, key)=> {
let orderFrame = new OrderFrame(val)
orderFrame.itemArray = await this.orderRepository.orderItems(val.id).find()
orderFrameArray.push(orderFrame)
})
orderFrameArray = await Promise.all(orderFrameArray)
return orderFrameArray
})
}
}
The function is returning before the orderFrameArray has been populated. I need four nested map loops and this first one is not working, so I am not sure how to do the rest. Any help would be extremely appreciated.
Based on #Tomalaks solution I tried the following, but its still only returning the top level array and nothing is nested:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(orders => {Promise.all(orders.map(
order => {
let orderFrame = new OrderFrame(order)
orderFrame.itemArray = new Array<Item>()
this.orderRepository.orderItems(order.id).find()
.then(orderItems => Promise.all(orderItems.map(
orderItem => {
let itemFrame = new Item(orderItem)
itemFrame.options = new Array<Option>()
this.orderItemRepository.orderItemOptions(orderItem.id).find()
.then(orderItemOptions => Promise.all(orderItemOptions.map(
orderItemOption => {
let optionFrame = new Option(orderItemOption)
optionFrame.values = new Array<Value>()
this.orderItemOptionRepository.orderItemOptionValues(orderItemOption.id).find()
.then(orderItemOptionValues => Promise.all(orderItemOptionValues.map(
orderItemOptionValue => {
let valueFrame = new Value(orderItemOptionValue)
optionFrame.values.push(valueFrame)})))
itemFrame.options.push(optionFrame)})))
orderFrame.itemArray.push(itemFrame)})))
orderFrameArray.push(orderFrame)}))
return orderFrameArray})
}
}
I apologize for the formatting I wasn't sure how best to format it. Is there something else I'm doing wrong?
Thanks to everyone for their response. The answer that was posted by #Tomalak was correct. I just had to surround the entire function in brackets, and put a .then to return the populated entity I had made
You only need to use async when you are using await in the same function. If there's await in a nested function, the parent function does not need async.
However, in your case, there is no function that should be made async in the first place.
There is no benefit in awaiting any results in your function, because no code inside depends on any intermediary result. Just return the promises as you get them.
There's no need for intermediary result variables like orderFrameArray, you're making things harder than they are with your approach of awaiting individual orders and pushing them to a top-level variable.
Using await in a loop like you do inside your .map() call is bad for performance. You are basically serializing database access this way – the next query will only be sent after the current one has returned. This kind of daisy-chaining nullifies the database's ability to process multiple concurrent requests.
getUserOrders2 is not Promise<any>, it's Promise<Array<OrderFrame>>.
throw terminates the function anyway, you can do multiple checks for error conditions without using else if. This reduces nesting.
So a fully asynchronous function would look like this:
getUserOrders2(#param.path.number('id') id: number): Promise<Array<OrderFrame>> {
if (!this.user) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
if (this.user.id != id) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
return this.restaurantRepository
.orders(this.user.restaurantId).find().then(
orders => Promise.all(orders.map(
order => this.orderRepository.orderItems(order.id).find().then(
order => new OrderFrame(order)
)
))
);
}
The async/await equivalent of this function would be more complex.
You then would await the result in the calling code, as you would have to do anyway:
async test() {
const orders = await foo.getUserOrders2(someUserId);
// ...
}
// or
test() {
foo.getUserOrders2(someUserId).then(orders => {
// ...
});
}
I don't know my question title is suitable for this question.
I would like to use returned value of someAsync1 (same as v2) as argument of action1 inside of flatMap.
const anEpic = ($action: ActionsObservable<MyAction>, store: Store<MyRootStore>) => {
return $action.ofType(ActionTypes.AN_ASYNC_ACTION)
.switchMap((v1) => someAsync1(v1))
.switchMap((v2) => someAsync2(v2))
.map((v) => applyToUI(v))
.flatMap((v) => Observable.concat(Observable.of(action1(v)), Observable.of(action2(true)))) //
}
I guess I can use that value by injecting v2 to returned value of someAsync2. But that code looks disgusting.
What is clever way to do this with redux-observable?
switchMap technically means, well, switch to another Observable stream. That means there is no way for you to retain the value because your observer is now observing a different source.
There are a few ways to do the so called "retaining" the values from one stream to another, depending on which one you prefer.
1. Behaviour Subject
This is the most preferred way, because the purpose of BehaviourSubject is to retain a value of an Observable:
//initialize a BehaviourSubject
let v2BSubject = new BehaviourSubject<any>(null);
const anEpic = ($action: ActionsObservable<MyAction>, store: Store<MyRootStore>) => {
return $action.ofType(ActionTypes.AN_ASYNC_ACTION)
.switchMap((v1) => someAsync1(v1))
.switchMap((v2) => {
//store your v2 value here!
v2BSubject.next(v2);
return someAsync2(v2)
})
.map((v) => applyToUI(v))
.flatMap((v) => {
//get your v2 values here
let v2Value = v2BSubject.value;
return Observable.concat(Observable.of(action1(v)), Observable.of(action2(true)))
}) //
}
or you can use it as an Observable. That way you can treat it as an observable and use whatever rxjs operator can provide:
.flatMap((v) => {
return Observable.concat(Observable.of(action1(v)), v2BSubject.asObservable())
})
2. Use .map to propagate the value.
This is rather hacky, but gets the job done. However do note it's modifying the stream source. If you have many operations along the pipe, it may blow up quickly and hard to manage:
const anEpic = ($action: ActionsObservable<MyAction>, store: Store<MyRootStore>) => {
return $action.ofType(ActionTypes.AN_ASYNC_ACTION)
.switchMap((v1) => someAsync1(v1))
.switchMap((v2) => {
someAsync2(v2)
.map(afterSomeAsync2 => {
return {
v1Value: v2,
v2Value: afterSomeAsync2
}
})
})
.map(({v1Value, v2Value}) => {
return applyToUI(v1Value).map(v1 => {
return {
v1Value: v1,
v2Value: v2Value
}
})
})
.flatMap(({v1Value, v2Value}) => {
return Observable.concat(Observable.of(action1(v1Value)), Observable.of(v2Value))
})
The easiest solution is to apply your operators directly on the returned inner Observables instead of on the collapsed outer chain. You can then access the values emitted because they're part of the closures.
That's probably confusing, but hopefully this code makes it clear:
const anEpic = ($action: ActionsObservable<MyAction>, store: Store<MyRootStore>) => {
return $action.ofType(ActionTypes.AN_ASYNC_ACTION)
.switchMap((v1) =>
someAsync1(v1)
.switchMap((v2) =>
someAsync2(v2)
.map((v) => applyToUI(v))
.flatMap((v) => Observable.of(action1(v, v1, v2), action2(true))
)
)
}
This pattern is also what you would have to use if you wanted to catch any errors by someAsync1 or someAsync2 because if you let the error propagate to the top-level chain the epic will have stopped listening for future actions.
e.g. if your epic looks like this:
const somethingEpic = (action$, store) => {
return action$.ofType(SOMETHING)
.switchMap(action => someAsync1(v1))
.map(() => ({ type: SOMETHING_FULFILLED }))
.catch(error => Observable.of({
type: SOMETHING_REJECTED,
error
}));
}
When the error reaches the catch operator it's too late, your epic is no longer listening for future actions. You could "restart" it, but this can have unexpected consequences so its best to avoid this pattern.
Instead, catch the error before it propagates out
const somethingEpic = (action$, store) => {
return action$.ofType(SOMETHING)
.switchMap(action =>
someAsync1(v1)
.map(() => ({ type: SOMETHING_FULFILLED }))
.catch(error => Observable.of({
type: SOMETHING_REJECTED,
error
}))
);
}
Some people refer to this as "isolating your observer chains".
Also notice how I didn't need to use concat with multiple of because of supports any number of arguments.
I am trying to learn more about the Http.Get. I am using a json file to mock the HTTP call. After the call I want to limit the rows to unique states. I know there is an Rxjs command called distinct (help on Rxjs distinct). However I do not understand the syntax for the distinct. When I run this code as is I get an array of states. However when I add the distinct it still has duplicated states like Texas.
public getStates(): Observable<IState[]> {
return this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
// distinct by state name
.distinct((x) => return x.state)
;
}
Here is the interface for IState
export interface IState {
id: number;
state: string;
city: string;
name: string;
}
Trying to only get rows with a unique state.
I have repo for the code on this Github project
You're confusing a bit the meaning of the distinct in this situation!
This distinct is meant to filter the response from your Observable, not its content! Here you're trying to "distinct" (filter) your json object, but the distinct will filter the responses (multiple) "resolved" by the Observable. So, if the observable "resolves" the same response multiple times, you'll receive only once, because of your .distinct.
This means, if the 1st time the Observable returns you back one json (e.g [{0: a, 1:b, 2:c}]) and the second time he returns the same json reponse, then your "subscribers" will not receive it twice, because the .distinct will "filter" it and as a result it will not be fired!
more details here: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinct.md
So, what you're looking for, is how to "filter" duplicated results within your json.
The answer is simple: use a loop to check its properties/values or some library like underscore which might have this method already implemented.
The IState is an object. You have to define what exactly makes them distinct from each other.You can look at the documentation, there is a way to define what makes an object distinct
For example:
/* Without key selector */
var source = Rx.Observable.of(42, 24, 42, 24)
.distinct();
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: 42
// => Next: 24
// => Completed
/* With key selector */
var source = Rx.Observable.of({value: 42}, {value: 24}, {value: 42}, {value: 24})
.distinct(function (x) { return x.value; });
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: { value: 42 }
// => Next: { value: 24 }
// => Completed
The problem is that when you're calling .distinct((x) => return x.state) the x variable is the entire array from https://github.com/ATXGearHead12/distinct/blob/master/src/api/state.json. Then property x.state doesn't exists.
So you need to call distinct on each item in the array not on the array itself.
For example like the following:
this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
.concatMap(arr =>
Observable.from(arr).distinct((x) => return x.state)
)
.toArray(); // collect all items into an array
I use Ionic 2 with Angular 2 in my project. In the root component you can click a "Add" button to add a new Report via a complex form and a lot of preprovided data (there are some selects that are feeded with data fetched from sqlite database)
Now in my "CreateReportComponent" i have the following constructor to load the data and assign it to local array variable:
selectEmployeeOptions: Employee[];
constructor(private dbService: DatabaseService) {
dbService.getAllEmployees().then(employees => {
this.selectEmployeeOptions = employees;
});
// load more data like tasks etc.
});
But when I want to modify this data in my component, the array is empty. I tried to do it in ngOnInit() but this seems to be to early as well.
I want to to something like this, before the component gets displayed:
dbService.getAllEmployees().then(employees => {
this.selectEmployeeOptions = employees;
// modify data
this.selectEmployeeTitleOptions = employees.map((item) => {
return item.title;
});
console.log(JSON.stringify(this.selectEmployeeTitleOptions)) // --> empty
});
But selectEmployeeTitleOptions is empty...
The function in the databaseService looks like this:
getAllEmployees(): Promise<Emplyoee[]> {
let query = "SELECT * FROM employees";
let employeeList = [];
this.database.executeSql(query, []).then((data) => {
if(data.rows.length > 0) {
let e = new Employee();
e.id = data.rows.item(i).id;
e.firstname = data.rows.item(i).firstname;
e.lastname = data.rows.item(i).lastname;
employeeList.push(e);
}
}, (error) => {
// handle error
});
return Promise.resolve(employeeList);
}
I read that there is the Resolve pattern (https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html) But I need to make multiple calls and not only for contacts as in the example.
So the question: How to wait for multiple calls to database?
i think something go wrong here
getAllEmployees(): Promise<Emplyoee[]> {
let query = "SELECT * FROM employees";
let employeeList = [];
this.database.executeSql(query, []).then((data) => {
if(data.rows.length > 0) {
let e = new Employee();
e.id = data.rows.item(i).id;
e.firstname = data.rows.item(i).firstname;
e.lastname = data.rows.item(i).lastname;
employeeList.push(e);
}
}, (error) => {
// handle error
});
return Promise.resolve(employeeList);
}
first return Promise.resolve(employeeList); will return empty array, because it is async process.
you need loop through data.rows, then format return data like this.
getAllEmployees(): Promise<Employee[]> {
let query = "SELECT * FROM employees";
return this.database.executeSql(query, []).then((data) => {
let arr = [];
for(let i = ; i < data.rows.length; ++i) {
let emp = data.rows.item(i);
let e = new Employee();
e.id = emp.id;
e.firstname = emp.firstname;
e.lastname = emp.lastname;
arr.push(e);
}
return arr;
});
}
note that .then() return a promise object.
What you are looking for is forkJoin method that returns Observable that you should switch to instead of using Promises, for reference about why you should do this check here.
Short information about fork join from its GitHub page:
Runs all observable sequences in parallel and collect their last elements.
This way you can safely make parallel requests to your API.
For more information regarding forkJoin go here.
Additionally you should call services using ngOnInit as you mentioned before. For more information about Angular 2 lifecycle hooks see the docs.
You can use Promise.all
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You push all promises to an array, and then go
let foo : [Promise<Emplyoee[]>,Promise<void>] = [getAllEmployees(), method2()];
Promise.all(foo).then((results:any[]) => {
let employeearray: any = results[0];
/* and on an on */
});
I found an example from angular.io. This example is very similar to my app, with same kind of methods. This example is using Promises, but I'm using Observables. If I use this example as a reference, I have every method working in my app, except the getHero method in the service, and the ngOnInit in the HeroDetailComponent. So I'm wondering if someone can help and convert this method to an observable, because I'm having trouble with the syntax. Here is the codes I need converted to Observable and the plunker
//HeroService
getHero(id: number) { // my id is String
return this.getHeroes()
.then(heroes => heroes.filter(hero => hero.id === id)[0]);
}
//HeroDetailComponent
ngOnInit() {
if (this.routeParams.get('id') !== null) {
let id = +this.routeParams.get('id');
this.navigated = true;
this.heroService.getHero(id)
.then(hero => this.hero = hero);
} else {
this.navigated = false;
this.hero = new Hero();
}
}
So I want something like this:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.subscribe(heroes => this.heroes.filter(hero => heroes.id === id)[0]); //BTW, what does this [0] mean??
}
EDIT: I had to actually retrieve the list directly, it didn't work with return this.heroes as suggested in answers below. Working example:
public getById(id: string) {
//return this.getHeroes() <---- didn't work
return this.http.get('someUrl') // WORKS!
.map(heroes => this.heroes.filter(hero => hero.id === id)[0]);
}
Now I'm still having trouble with my ngOnit, and I can't really understand why!
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
//console.log("retrieved id: ",id ) <----- gives correct id!
.subscribe(hero => this.hero = hero);
//console.log("hero: ", this.hero); <----- gives undefined!
}
EDIT2, still getting undefined when trying to move to the detail page :( I think you had one bracket to much in your answer, tried to look and get the correct places for the brackets?
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
.subscribe(heroes => {
// this code is executed when the response from the server arrives
this.hero = hero
});
// code here is executed before code from the server arrives
// even though it is written below
}
If you call subscribe() on an Observable a Subscription is returned. You can't call subscribe() on a subscription.
Instead use just an operator (map()) and use subscribe() on the call site:
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
In contrary to subscribe(), map() also operates on an Observable but also returns an Observable.
[0] means to just take the first item of the filtered heroes.
update
ngOnInit(){
let id = this._routeParams.get('id');
this._searchService.getById(id)
.subscribe(searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase;
console.log("id: ", this.searchCase);
});
// code here is executed before code from the server arrives
// event though it is written below
}
This code is a function
searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase);
console.log("id: ", this.searchCase);
}
that is passed to subscribe() and the Observable calls this function when it has new data for the subscriber. Therefore this code is not executed immediately but only when the observable emits new data.
Code that comes after subscribe() is executed immediately and therefore before above function and therefore this.searchCase does not yet have a value.
This is a way you can do it:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
//HeroDetailComponent
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => {
// your code here
});
}
The [0] is an array accessor. You're selecting the first element on array index 0 with it. You need this, because Array.filter() returns a new array with the filtered values, but you only want one hero.