So I was working on a new component in Angular and in the ngOninit I have the following asynchronous functions below...
This.getUserProfile needs to be finished before I can call this.getPrivateGroup() and this.getPrivateGroup() needs to be finished before I can call this.loadGroupPosts(). I know I could write these functions inside the callback of the asynchronous requests, but I was wondering if there is a way to keep it in ngOnInit to keep it cleaner?
Anyone has an idea?
ngOnInit() {
this.getUserProfile();
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
this.loadGroupPosts();
}
getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
});
}
getPrivateGroup() {
console.log('user check', this.user);
this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
.subscribe((group) => {
console.log('received response', group)
})
}
// !--LOAD ALL THE GROUP POSTS ON INIT--! //
loadGroupPosts() {
this.isLoading$.next(true);
this.postService.getGroupPosts(this.group_id)
.subscribe((res) => {
// console.log('Group posts:', res);
this.posts = res['posts'];
console.log('Group posts:', this.posts);
this.isLoading$.next(false);
this.show_new_posts_badge = 0;
}, (err) => {
swal("Error!", "Error while retrieving the posts " + err, "danger");
});
}
// !--LOAD ALL THE GROUP POSTS ON INIT--! //
You can use basic promises with async/await.
async ngOnInit() {
await this.getUserProfile(); // <-- 1. change
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
this.loadGroupPosts();
}
async getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
return true; // <-- this
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
throw err;
});
}
You could instead leverage RxJS and use a switchMap something like this (syntax NOT checked):
getData(): Observable<string[]> {
return this._userService.getUser()
.pipe(
switchMap(userInfo=> {
return this.getPrivateGroup();
}),
catchError(this.someErrorHandler)
);
}
One way to do is, return the Observable instead of subscribing in the getPrivateGroup()
getPrivateGroup() {
console.log('user check', this.user);
return this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
}
And then, subscribe to the data where you want the chain the this.loadGroupPosts()
if (this.isItMyWorkplace) {
this.getPrivateGroup().subscribe(group => {
this.group = group; //you probably want to assign the group data
this.loadGroupPosts()});
}
you could also use the 3rd part of your subscribe function when its completed
i am not quite sure if this is a clean solution, in my opinion it is.
ngOnInit() {
this.getUserProfile();
}
getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
}, () => {
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
});
}
getPrivateGroup() {
console.log('user check', this.user);
this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
.subscribe((group) => {
console.log('received response', group)
}, error => {
console.log(error)
}, () => {
this.loadGroupPosts();
})
}
loadGroupPosts() {
this.isLoading$.next(true);
this.postService.getGroupPosts(this.group_id)
.subscribe((res) => {
// console.log('Group posts:', res);
this.posts = res['posts'];
console.log('Group posts:', this.posts);
this.isLoading$.next(false);
this.show_new_posts_badge = 0;
}, (err) => {
swal("Error!", "Error while retrieving the posts " + err, "danger");
});
}
Related
I have 2 pages in my controller folder like this:
General.js
const Person = require('../models/person');
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
module.exports.personInfo = personInfo;
Account.js
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
return res.render('/myPage/Account', {
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); });
}
Problem is , this promise working when server beginning but only once after its not working , par value always being same. If i change datas on my database , datas not changing on my web page.
anti-pattern
This is the explicit promise construction anti-pattern -
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
You can replace it with -
let personInfo = Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
.catch(console.error) // <- don't catch here
And you should probably leave the .catch off and expect the caller to handle error handling. The .catch handler below would never trigger if the error is catch'd before
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
res.render('/myPage/Account', { // no "return" needed
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); }); // <- keep catch here
}
The reason it is only happening once, is because Promises can only be resolved or rejected once. You'll have to replace personInfo with a function -
const Person = require('../models/person');
const fetchPersonInfo = () =>
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
});
module.exports.fetchPersonInfo = fetchPersonInfo;
async await
You might also want to consider reading up on async/await as they make your life a lot better
const Person = require('../models/person');
async function fetchPersonInfo () { // <- async
const par = await Person.find({ Group: 'pre'}) // <- await
if (par.length > 0)
return par;
else
throw Error("Error: empty par");
});
module.exports.fetchPersonInfo = fetchPersonInfo;
const { fetchPersonInfo } = new require('./general'); // <- top-level import
exports.Ac = async (req, res, next) => { // <- async
try {
const par = await fetchPersonInfo() // <- await
res.render('/myPage/Account', {
title: 'Account',
group: par
});
} catch (err) {
console.log(err)
}
}
Is there a better way to orchestrate the below code in Rxjs ?
and from Javascript perspective Promise/Asyc/Await which will be better considering some hard error and soft error handlings.
Couple of calls are hard dependency which are marked as hard true in the error and on subsequent error handler checks and re throw it. and soft errors are added to final data.error for any processing based on the soft errors.
How this can be better done ?
const { Observable } = require('rxjs');
const { map, filter, flatMap, concatMap } = require('rxjs/operators');
function testcallback(fail, callback) {
if (fail == true) {
setTimeout(function(){ callback(new Error({ message: "Operation failed" })); }, 100);
}
else {
setTimeout(function(){ callback(null, 10); }, 100);
}
}
function createData(param) {
return Observable.bindNodeCallback(testcallback)(param);
}
function associateData(param) {
return Observable.bindNodeCallback(testcallback)(param);
}
function removeData(param) {
return Observable.bindNodeCallback(testcallback)(param);
}
function markAssociateSuccess(param) {
return Observable.bindNodeCallback(testcallback)(param);
}
function removeOldData(param) {
return Observable.bindNodeCallback(testcallback)(param);
}
function getAllData() {
return Observable.of( {error:null, data:[333]});
}
function updateData(updateData) {
let param = updateRequest.param;
return createData(1 == param)
.catch((err) => {
err.errorStep = 'CREATE_DATA';
err.hard = true;
throw (err);
}).flatMap(
() => {
return associateData(2 == param)
.catch((err) => {
err.errorStep = 'ASSOCIATE_DATA';
err.hard = true;
return removeData(3==param)
.map(() => {
throw (err);
});
});
},
(createDataResponse, associateDataResponse) => {
return [createDataResponse, associateDataResponse];
}
)
.flatMap(() => {
if (updateData.markAssociationSuccess) {
return markAssociateSuccess(4 == param);
} else {
return Observable.of({});
}
}).catch((err) => {
if (err.hard) {
throw (err);
}
err.errorStep = 'ASSOCIATE_SUCCESS';
return Observable.of({ error: err });
})
.flatMap((data) => {
if (data.error) {
return Observable.of(data);
}
return removeOldData(5 == param);
}).catch((err) => {
if (err.hard) {
throw (err);
}
err.errorStep = 'REMOVE_OLD_DATA';
return Observable.of({ error: err });
})
.flatMap(
function fetchData() {
return getAllData();
},
function resultSelector(prevRes, { error, data }) {
if (error) {
return {};
}
if (prevRes.error) {
data.error = prevRes.error;
}
return data;
}
)
.subscribe(
function onNext(data) {
console.log("Successful operation final data: " + data);
},
function onError(err) {
console.log("Errored out" + JSON.stringify(err));
},
function onComplete() {
console.log("Stream got completed");
}
);
}
const updateRequest = {
param:1,
markAssociationSuccess: true
}
updateData(updateRequest);
I have a function that returns a value called user_id. But there are many conditions to be checked.
condition 1: check service variable
condition 2: If no, get user_id from localstorage
condition 3: If no, get firebase_uid from localstorage, and call function findUserId(firebase_uid) which will return user_id
condition 4: if no, get uid from firebase and call findUserId(uid)
here is the code.
export class UserService {
user_id : any;
firebase_uid: any;
id: any;
returnUser_id() {
if(this.user_id) {
return this.user_id
}
else {
this.storageService.get('user_id').then(val =>{
if(val) {
this.user_id =val
return this.user_id
}
else {
this.storageService.get('firebase_uid').then(value =>{
if(value) {
this.firebase_uid = value
this.findUserId(this.firebase_uid).subscribe(q =>{
console.log(q)
this.id = q;
for(let i =0; i<this.id.length;i++) {
this.user_id = this.id[i].id
return this.user_id
}
this.storageService.set('user_id', this.user_id ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
})
}
else {
this.afauth.authState.subscribe(user =>{
if(user) {
this.firebase_uid = user.uid;
this.storageService.set('firebase_uid', this.firebase_uid ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
this.findUserId(this.firebase_uid).subscribe(data =>{
this.id = data;
for(let i = 0 ;i<this.id.length; i++ ){
this.user_id = this.id[i].id
return this.user_id
}
this.storageService.set('user_id', this.user_id ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
})
}
})
}
}).catch(err =>{
console.log(err)
})
}
}).catch(err =>{
console.log(err)
})
}
}
}
findUserId function
findUserId(uid): Observable<any> {
return this.http.get<User[]>(this.user_url + 'users?filter[fields][id]=true&filter[where][firebase_uid]=' +uid )
}
This code is so complex and difficult to understand. Is there any alternative to traditional if else statements.
Thank you in advance
There are some repeatable code, so we can move repeatable code into method and reuse it. Moreover we can simplify our code using async keywords.
if(this.user_id)
return this.user_id;
else {
let user_id = await this.storageService.get('user_id');
if (user_id)
return user_id;
else {
let firebase_uid = await this.storageService.get('firebase_uid');
if (firebase_uid) {
await reusableFindUserId(firebase_uid);
if (this.user_id)
await setStorageService();
}
else {
let user = await this.afauth.authState();
if (user) {
this.firebase_uid = user.uid;
await reusableFindUserId(firebase_uid);
if (this.user_id)
await setStorageService();
}
})
}
}
}
and reusable methods:
async reusableFindUserId(firebase_uid){
this.id = await this.findUserId(firebase_uid);
for(let i =0; i<this.id.length;i++) {
this.user_id = this.id[i].id;
return this.user_id;
}
}
async setStorageService() {
return await this.storageService.set('user_id', this.user_id );
}
You can get rid of this else, because you return in the if-block above. If you don't return, the remaining code will be executed.
You can go through the hole function and check if the elses are necessary. If you return, you don't need an else :)
Another point is, you can extract some parts of the code into dedicated functions. Your main function will be much cleaner and shorter.
if(val) {
this.user_id =val
return this.user_id
}
// No need for the else...
else {
....
}
I am doing with Ionic News App. I have one problem with getting a response from the service file.
home.page.ts
getNews(): void {
this.loading = true;
this.language = localStorage.language;
this.checkForToken();
var userId = this.loggedInUser;
this._newsService.getAllNews().subscribe(
(res: any) => {
console.log("all news==========>", res)
this.loadNewsToPage(res, userId);
},
(err) => {
this.loading = false;
this.error = err;
});
}
news.service.ts
getAllNews(){
if(this.network.type == 'none' ){
console.log(JSON.parse(localStorage.getItem("newsArray")));
this.newsArray = JSON.parse(localStorage.getItem("newsArray"))
return this.newsArray;
}else{
return this.http.get(config.baseApiUrl + 'news?isApproved=APPROVED').pipe(
map((res) => {
this.newsArray = res['data'];
localStorage.setItem('newsArray',JSON.stringify(this.newsArray))
return this.newsArray;
}),
catchError(this.handleError));
}
}
Now the problem is when the network is 'none' it goes in 'if' condition in service file and return response from local storage. But it gives me below error when network is none.
ERROR TypeError: this._newsService.getAllNews(...).subscribe is not a
function
It works properly when it goes in else condition or when a network is present. Why like this?
Your getAllNews function isn't an Observable. So you can't subscribe to it. See the example below where you return an Observable for the first if condition and the second else condition. You need to close the Observable with observer.complete() after each next function.
getAllNews(): Observable<any>{
return new Observable(observer => {
if(this.network.type == 'none' ){
console.log(JSON.parse(localStorage.getItem("newsArray")));
this.newsArray = JSON.parse(localStorage.getItem("newsArray"))
observer.next(this.newsArray);
observer.complete();
} else{
this.http.get(config.baseApiUrl + 'news?isApproved=APPROVED').subscribe(
(result: object) => {
this.newsArray = result['data'];
localStorage.setItem('newsArray',JSON.stringify(this.newsArray))
observer.next(this.newsArray);
observer.complete();
},
(error) => {
observer.error(error);
});
}
});
}
You can now access the next block in your subscribe > result and the error block in subscribing> error.
this._newsService.getAllNews().subscribe(
(res: any) => { // observer.next() brings you here
console.log("all news==========>", res)
this.loadNewsToPage(res, userId);
},
(err) => { // observer.error() brings you here
this.loading = false;
this.error = err;
});
Always when I try to join a Group Channel, I am presented with this error
code: 800101
message: "Connection should be made first."
name: "SendBirdException"
Here is my code for connecting to Group Channel.
I am connecting to the SendBird with connect in another function async, and only then calling the connectToChat
The group is created by another user, and this is the code for when a user tries to join that group.
User Connection works fine.
Group retrieval works fine.
But when I try to connect to the group, it errors out.
public connectToChat(chatId: string) {
return new Promise((s, e) => {
const sendBirdEngine = SendBird.getInstance();
try {
if (ChatService.isReady) {
this.log("Connecting to chat...", chatId);
sendBirdEngine.GroupChannel.getChannel(
chatId,
(groupChannel, error) => {
if (error) {
this.logError(
"An error occured while getting channel",
chatId,
error
);
return;
}
this.log("Got the channel!", chatId, groupChannel);
if (groupChannel.isPublic) {
groupChannel.join((response, err) => {
this.log(
"groupChannel Join",
response
// Always getting error here
);
if (err) {
this.logError("connectToChat", err);
e(false);
return;
} else {
s(true);
this.log("Joined the Chat!", chatId);
}
ChatService.chatRooms[
chatId
] = groupChannel;
this.log(
"Successfully Cached the Channel",
chatId
);
});
} else {
this.logError("[ERROR] Channel is Private");
}
// s(true);
}
);
} else {
this.logError("[ERROR] Chat Service is not ready");
}
} catch (err) {
e(err);
}
});
}
EDIT: Added Full Class File for complete reference
class ChatService {
public static userId: string;
public static chatRooms: {
[index: string]: SendBird.BaseChannel;
} = {};
private static isReady: boolean = false;
constructor(userId ? : string) {
if (userId && !ChatService.userId) {
ChatService.userId = userId;
} else {
this.log("userId already set", ChatService.userId);
}
}
/**
* create
*/
public create() {
return new Promise((s, e) => {
if (!ChatService.isReady) {
// connecting to sendbird here
const sendBirdEngine = new SendBird({
appId: "XXXXX-XXXXXX-XXXXXX",
});
this.log("Engine Initialised!", sendBirdEngine);
// init the user
this.initialiseUser((data: any) => {
s(data);
});
}
});
}
/**
* initialise
*/
public async initialiseUser(onReadyHandler: any) {
const userId = ChatService.userId;
this.log("Starting ChatService", userId);
try {
this.connectUserToEngine((res: any) => {
this.log("connectUser() callback", res);
ChatService.isReady = true;
// this.getListOfChatRooms();
onReadyHandler(true);
});
} catch (err) {
onReadyHandler(false);
this.log("[ChatService Error]", err);
}
}
/**
* connects user to engine
*/
public connectUserToEngine(callback: any) {
const sendBirdEngine = SendBird.getInstance();
const userId = ChatService.userId;
this.log("Connecting user...", userId);
sendBirdEngine.connect(userId, (user: any, error: any) => {
if (error) {
this.log("[Error]", error);
this.log("Reconnecting User in 5 seconds...");
setTimeout(() => {
this.connectUserToEngine(callback);
}, 5000);
return;
} else {
this.log("User Connected", user);
callback(user);
}
});
}
/**
* connect to a particular chat
*/
public connectToChat(chatId: string, onNewMessageListener: any) {
return new Promise((s, e) => {
const sendBirdEngine = SendBird.getInstance();
this.log("Current User", sendBirdEngine.currentUser);
try {
if (ChatService.isReady) {
this.log("Connecting to chat...", chatId);
// this.connectUserToEngine(() => {
sendBirdEngine.GroupChannel.getChannel(
chatId,
(groupChannel, error) => {
if (error) {
this.logError(
"An error occured while getting channel",
chatId,
error
);
return;
}
this.log("Got the channel!", chatId, groupChannel);
if (groupChannel.isPublic) {
groupChannel.join((response, err) => {
this.log(
"groupChannel Join",
response
// err
);
// FIXME: Assuming it always works
if (err) {
this.logError("connectToChat", err);
e(false);
return;
} else {
s(true);
this.log("Joined the Chat!", chatId);
}
ChatService.chatRooms[
chatId
] = groupChannel;
this.log(
"Successfully Cached the Channel",
chatId
);
});
} else {
this.logError("[ERROR] Channel is Private");
}
// s(true);
}
);
// });
} else {
this.logError("[ERROR] Chat Service is not ready");
}
} catch (err) {
e(err);
}
});
}
/**
* connects to all chat rooms
*/
public async connectToAllChatRooms(
chatRooms: string[],
onNewMessageListener: any
) {
try {
this.log("connectToAllChatRooms()", chatRooms);
// connect to all chat rooms
for (const chatRoom of chatRooms) {
const x = await this.connectToChat(
chatRoom,
onNewMessageListener
);
}
this.log("connectToAllChatRooms() done");
return true;
} catch (err) {
this.logError("connectToAllChatRooms", err);
throw new Error(err);
}
}
export default ChatService;
I took a look at your code. You ChatService class might need a little change.
Idea 1
On the ChatService class you have a create() method that is not returning a promise, if a user already exists.
public create() {
return new Promise((s, e) => {
if (!ChatService.isReady) {
// connecting to sendbird here
const sendBirdEngine = new SendBird({
appId: "APP_ID"
});
this.log("Engine Initialised!", sendBirdEngine);
// init the user
this.initialiseUser((data: any) => {
s(data);
});
} else {
// Resolve promise when user already exists
s("Already Connected!");
}
});
}
From there it seems to work as expected. Correct me if I'm wrong but this is how I implement your class.
const initChat = () => {
const url = "CHANNEL_URL";
const chat = new ChatService("USER_ID");
chat.create().then(res => {
chat.connectToChat(url).then((res)=>{
console.log("DONE", res)
})
});
};
Idea 2
Also: Perhaps check to see if you are calling
sendBirdEngine.GroupChannel.getChannel()
Before the connection to the user has completed.
Example
Here is a working example of your code if needed. It needs:
index.js - CHANNEL_URL and USER_ID
ChatService.ts - APP_ID