I have written a generic method like this:
getArticleById(loading: Loading): void {
this.articleService.getArticleById(this.data.definition.id)
.map((res: any) => res.json())
.subscribe((res: any) => {
if (res.definition.is_purchased) {
//more code
} else {
//more code
}
loading.dismiss();
} else {
loading.dismiss();
}
}, () => { loading.dismiss(); });
}
Parent method (or calling) is like this:
myParentMethod(){
const loading = this.loader.create({
content: 'loading...'
});
loading.present();
this.getArticleById(loading);//can I call the `loading.dismiss()` here.
}
I would like to remove the loading parameter from the genric method(getArticleById()) and need to put that inside the parent method(myParentMethod()) after resolving the subscription.Can you tell me how to do that?
To handle the observable terminating at the higher level, you'll need to return an observable that the higher-level function can access.
Try writing it like this:
getArticleById(loading: Loading) {
const articles$ = this.articleService.getArticleById(this.data.definition.id)
.map((res: any) => res.json());
article$.subscribe((res: any) => {
if (res.definition.is_purchased) {
//more code
} else {
//more code
}
});
return article$;
}
finally is a useful operator which
Invokes a specified action after the source observable sequence terminates gracefully or exceptionally.
myParentMethod(){
const loading = this.loader.create({
content: 'loading...'
});
loading.present();
this.getArticleById().finally(() => loading.dismiss());
}
However, this code is still structured a bit awkwardly. I'd separate out the logic to get the observable from that to handle it, and write the code as follows:
getArticleById(): Observable<Article> {
return this.articleService.getArticleById(this.data.definition.id)
.map(res => res.json());
}
handleArticle(article) {
if (article.definition.is_purchased) {
//more code
} else {
//more code
}
}
myParentMethod(){
const loading = this.loader.create({
content: 'loading...'
});
const article$ = this.getArticleById();
loading.present();
article$
.finally(() => loading.dismiss())
.subscribe(article => this.handleArticle(article));
}
Related
I have a code with multiple promise chain as shown below
.then(function(response) {
//my code
})
.then(function(app) {
//my code
})
.then(function() {
//my code
})
Have added exception handling to each of them as shown below so that if one breaks the rest chain continues.
Is this the correct way of handling exception for multiple chain blocks, or any best practice can be followed to handle the exceptions so that the code execution doesn't break if one fails.
.then(function(response) {
//my code
})
.catch(e => {})
.then(function(app) {
//my code
})
.catch(e => {})
.then(function() {
//my code
})
.catch(e => {})
If your code can accommodate the error (whatever it is) that's occurring early on, this can be a reasonable way of doing it, but I'd always have to look twice at it in a code review because it's fairly unusual for the code to be able to just ignore errors like that. The code is roughly equivalent to:
try {
//my code
} catch (e) {
}
try {
//my code
} catch(e) {
}
try {
//my code
} catch(e) {
}
...but using promises instead. So it's a bit suspect, but can be correct, for the same reasons the above is a bit suspect but can be correct if you need to do a series of things, one at a time, and have each of them done even if the previous one fails.
Beware that it means app in the subsequent fulfillment handler will be undefined:
.then(function(response) {
//my code
})
.catch(e => {})
.then(function(app) { // <=== `app` is `undefined` here
//my code
})
.catch(e => {})
.then(function() {
//my code
})
.catch(e => {})
Answer posted before, is correct.
I just want to insert my 2 cents.
You can have some fun with one helper function (or use something more fancy like whole Either monad thingy from fp-ts package)
const run = async (fn) => {
try {
const result = await fn()
return [result, null]
} catch (err) {
return [null, err]
}
}
and write code without try\catch or .then\.catch
const [response, error] = await run(() => fetch('asdfasdf'))
const app = buildApp(response.ok ? await response.json() : { fallback: 'data' })
const [appResponse, appError] = await run(async () => {
await app.compileTemplate()
return app.buildResponse()
})
if (appResponse) {
// ...
}
Or more useless approach, you can throw a custom error, so your first .catch block will be able to do something.
class AppFromResponseError extends Error {
constructor(message, fallbackApp) {
super(message)
this.name = "ResponseError"
this.app = "fallbackApp"
}
}
builder
.then(function(response) {
if (response.ok !== true)
throw new AppFromResponseError('not ok', minimalApp)
})
.catch(e => {
if (e.app) return e.app
})
.then(app => { /* some kind of app will be here */})
I have this method that I copied from a websocket tutorial but I don't understand the meaning of the "return () => { ... }" inside the observable ? Can someone explain me what is the purpose of that ?
public onMessage(topic: string, handler = SocketClientService.jsonHandler) : Observable<any> {
return this.connect().pipe(first(), switchMap(client => {
return new Observable<any>(observer => {
const subscription : StompSubscription = client.subscribe(topic, message => {
observer.next(handler(message));
});
return () => {
console.log("Unsubscribe from socket-client service");
client.unsubscribe(subscription .id);
}
});
}));
}
In order to create an Observable, you can use new Observable or a creation operator. See the following example:
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
});
You can provide a function unsubscribe() to allow dispose of resources, and that function goes inside subscribe() as follows:
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
return function unsubscribe() {
console.log('Clearing resources on observable');
};
});
Of course, you can use an arrow function expression to have:
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
return () => {
console.log('Clearing resources on observable');
};
});
Try the following code to test the Observable:
const subscription = observable.subscribe(res => console.log('observable data:', res));
subscription.unsubscribe();
Finally, subscription.unsubscribe() is going to remove the socket connection in your example.
Find a project running with these examples here: https://stackblitz.com/edit/typescript-observable-unsubscribe
Let me know if that helps!
I want to call an endpoint, then when the data is retrieved, call a function, using that data. As the data must be available to be used with that function!
I'm sorry if the code appears convoluted, it is a bit complex.
The function I want to call looks like this:
potentialEventsData() {
this.allEngagementTypes().subscribe(data => {
const products = {};
data.forEach(product => {
products[product.id] = product.description;
this.potentialForEvents(this.organization.id, product.id)
.subscribe(potentialEventData => {
console.log('consoling the potential event data' + potentialEventData) <---- this potential event data is what I want to use and parse!
});
});
});
}
So this potentialEventData I want to parse into this function below:
(apologies, its quite a big function)
I want to call the above function, or simply just call the code, then when I have the data returned, parse the potentialEventData into this function, Now actually what this function does is return an array for each item as its a for loop so really I need to concatenate that first (sorry just realised) then send that one array into the this.buildOrganization(updatedGraphTable, resolve) function as an extra parameter, I've tried many ways to do this but I haven't yet managed a way to do it!
graph(id: string): Promise<boolean> {
this.organization.setValues(id);
return new Promise((resolve, reject) => {
if (!this.reload) {
resolve(false);
} else {
let areasLoaded = false;
let root: IEngagementGraphNode;
this.getAllProductGroups().then(() => {
areasLoaded = true;
this.buildOrganization(root, resolve);
}).catch(() => {
areasLoaded = true;
this.buildOrganization(root, resolve);
});
Observable.zip(this.organizationEngagements(id, this.requestOptions()), this.organizationIndividuals(id, this.requestOptions()))
.subscribe(([graphData, individualsData]) => {
this.insight.orgsMainName = graphData.name
if (areasLoaded) {
return this.buildOrganization(updatedGraphTable, resolve); <------ here!
} else {
root = updatedGraphTable;
}
}, error => reject(error));
}
});
}
Also the buildOrganization function:
buildOrganization(root, resolve): void {
if (root) {
this.organization.clear();
this.organization.setProperties(root, this.filterParams);
this.focus = this.organization;
if (this.userList.length === 0) {
this.userList = this.organization.individuals;
}
this.reload = false;
this.data();
resolve(true);
}
}
If you can help that would be amazing, thank you!
I have trouble to get this to work.
I have a function getItem:
export const getItem = async (key, callback) => {
value = await Expo.SecureStore.getItemAsync(key).catch((error) => console.log(error));
callback(value);
}
getItem is supposd to get a token and pass that token to the callback.
Now I want to use getItem in this (simplified) class:
export class Post {
constructor(){
this.token = false;
}
post() {
console.log('Token: ' + this.token);
...
}
setToken(token){
console.log('Set token.');
this.token = token;
}
authorizedPost() {
getItem('token', this.setToken.bind(this)).then(
this.post()
);
}
}
I use this class like this:
let post = new Post();
post.authorizedPost();
This is the output I get:
Token: false
Set token.
But I need the token to be set and after that, I want to call the method this.post();
Since I'm a beginner, I want to excuse if that issue is trivial. But I am thankful for every help!
I had to pass a function to then like this:
authorizedPost() {
getItem('token', this.setToken.bind(this)).then(
()=>this.post()
);
}
I know you didn't really ask, but…
Part of the reason the code is difficult to debug is because you are using callbacks when you don't need to and mixing them with promises. These are two different async paradigms that are normally better off separate. You can get rid of the callback in your code in a way that will make it much more readable.
Expo.SecureStore.getItemAsync()
returns a promise, so just return it.
const getItem = (key) => Expo.SecureStore.getItemAsync(key);
Then in your method you can call then and simply call the function you were passing as a callback. No need for callback or bind. Just one line after the other:
authorizedPost() {
getItem('token').then(val => {
this.setToken(val) // the order is now obvious
this.post()
})
.catch((error) => console.log(error))
}
Here's a snippet with a faked Expo method:
let Expo = {
SecureStore: {
getItemAsync() {
return new Promise((resolve, reject) => setTimeout(() => resolve("someFancyToken"), 1000))
}
}
}
class Post {
constructor() {
this.token = false;
}
post() {
console.log('Token: ' + this.token);
}
setToken(token) {
console.log('Set token.');
this.token = token;
}
authorizedPost() {
getItem('token').then(val => {
this.setToken(val)
this.post()
})
.catch((error) => console.log(error))
}
}
const getItem = (key) => Expo.SecureStore.getItemAsync(key);
let post = new Post();
post.authorizedPost();
I trying to read an item (menulist) from localStorage. If it is null, I am calling a service which will store menulist after fetching it from database).
It seems that service is asynchronous as I am getting Cannot read property 'sort' of null in the following code.
ngOnInit() {
this.menulist = localStorage.getItem('menulist');
if (!this.menulist) {
this.SetMenuList();
}
this.jsonmenulist = JSON.parse(this.menulist);
this.jsonmenulist.sort(function (a, b) {
return a.mnuposno > b.mnuposno;
});
this.jsonmenulist = this.jsonmenulist.sort();
}
SetMenuList() {
this._UserspecificmenuaccessService.getRMA("driver")
.subscribe((lst) => {
if (lst && lst.length > 0) {
localStorage.setItem('menulist', JSON.stringify(lst));
this.menulist = localStorage.getItem('menulist');
console.log(this.menulist); // gets called after this.jsonmenulist.sort?
return true;
}
}, (error) => {
console.error(error)
});
}
Console:
ERROR TypeError: Cannot read property 'sort' of null
[{"mnurecid":"4","mnuid":"menu1","mnuname":"Bin","mnuurl":"/bin/","mnuposno":"1.0","checked":true}, {"mnurecid":"12","mnuid":"menu9","mnuname":"Menu9","mnuurl":"/menu9","mnuposno":"9.0","checked":false}]
You can use toPromise() method from rxjs library to return promise rather than observable.
So In your service
getRMA(type) {
return this.http.post(environment.baseURL + '/getmenulist', { drive:
type }, this.options).map((response) => response.json()
).toPromise().catch(e => {
console.log(e);
)}
}
and In your component use async and await
async SetMenuList() {
let response = await
this._UserspecificmenuaccessService.getRMA("driver")
console.log(response)
}
Not exactly about async broblem, you just use the this.menulist before it's assigned. Just change the way you run your codes.
ngOnInit() {
this.menulist = localStorage.getItem('menulist');
if (this.menulist) {
this.sortMenuList(); // Sorting the menu if we have menulist already
} else {
this.SetMenuList(); // Else call the service and sort the menu then
}
}
sortMenuList() {
this.jsonmenulist = JSON.parse(this.menulist);
this.jsonmenulist.sort(function (a, b) {
return a.mnuposno > b.mnuposno;
});
this.jsonmenulist = this.jsonmenulist.sort();
}
SetMenuList() {
this._UserspecificmenuaccessService.getRMA("driver")
.subscribe((lst) => {
if (lst && lst.length > 0) {
localStorage.setItem('menulist', JSON.stringify(lst));
this.menulist = localStorage.getItem('menulist');
this.sortMenuList(); // Sorting the menu if we have menulist already
}
}, (error) => {
console.error(error)
});
}
By the way, SetMenuList naming should be setMenuList (Just recommended for naming).
You could write the code that need to be executed inside the subscribe function so that it is executed only after the asynchronous operation is done.