I have four subscribes that dependable on each other. I know there are too many answers regarding avoiding nested subscribe. But nobody answers more than two levels.
how to avoid that many nested subscribe
this is my component code
if (this.code) {
this.appService.updateData('Users/clever_token', {code: this.code}).subscribe(data => {
if (data.detail) {
this.accessToken = data.detail.access_token;
this.appService.getCleverData('/v2.1/me', this.accessToken).subscribe(data1 => {
if (data1.links) {
this.userData.user_type = data1.type;
this.appService.getCleverData(data1.links[1].uri, this.accessToken).subscribe(data2 => {
if (data2.data) {
this.userData.name = data2.data.name.first + ' ' + data2.data.name.last;
this.userData.clever_id = data2.data.id;
this.userData.email = data2.data.email;
this.appService.updateData('Users/cleaver_login', this.userData).subscribe(data3 => {
if (data3.detail) {
console.log(data3.detail);
}
});
}
});
}
});
}
});
}
this is service code
getCleverData(url, token) {
let reqHeader = new HttpHeaders({
'Authorization': 'Bearer ' + token
})
return this.http.get(API_PREFIX + url, { headers: reqHeader })
.pipe(
map((data: any) => {
console.log(data);
if (data) return data;
}),
catchError(this.handleError)
);
}
/** PUT: update a data to the server */
updateData (url, data?) {
let httpParams = new HttpParams();
Object.keys(data).forEach(function (key) {
httpParams = httpParams.append(key, data[key]);
});
return this.http.post(this.apiUrl + url, httpParams, httpOptions)
.pipe(
map((data: any) => {
if (data.status == 0) {
this.presentToast(data.message);
}
if (data) return data;
}),
catchError(this.handleError)
);
}
is there any way to avoid that many subscribe. I can't remove any of it because its some of it from our server and others are third party
There are a few options but as I see you need that structure because you need data from the previous observable so you can use filter() and switchMap().
filter() is able to filter out values which do not contain necessary values switchMap() - to switch to a new stream.
UPDATE:
There is a refactored version of your code:
if (!this.code) {
return;
}
this.appService.updateData('Users/clever_token', {code: this.code}).pipe(
filter(data => !!data.detail),
switchMap(data => {
this.accessToken = data.detail.access_token;
return this.appService.getCleverData('/v2.1/me', this.accessToken);
}),
filter(data1 => !!data1.links),
switchMap(data1 => {
this.userData.user_type = data1.type;
return this.appService.getCleverData(data1.links[1].uri, this.accessToken);
}),
filter(data2 => !!data2.data),
switchMap(data1 => {
this.userData.name = data2.data.name.first + ' ' + data2.data.name.last;
this.userData.clever_id = data2.data.id;
this.userData.email = data2.data.email;
return this.appService.updateData('Users/cleaver_login', this.userData);
}),
).subscribe(data3 => {
if (data3.detail) {
console.log(data3.detail);
}
}
Related
Without getting response from const thumbnailSource = this.getThumbnailImage(); next line get excecuted, after getting response from getThumbnailImage() have to execute next line
constructor(private httpClient: HttpClient) {}
const defaultThumbnail: File[] = [];
const defaultArticle: File[] = [];
createArticle(articleData: IArticleData, thumbnail?: File | string, articleImage?: File | string) {
const defaultThumbnail: File[] = [];
const defaultArticle: File[] = [];
const formData = new FormData();
formData.append('title', articleData.title);
formData.append('author', articleData.author);
formData.append('articleData', articleData.articleData);
formData.append('published', JSON.stringify(articleData.published));
if (thumbnail) {
formData.append('thumbnail', thumbnail);
}
if (articleImage) {
formData.append('articleImage', articleImage);
}
if (!thumbnail) {
const thumbnailSource = this.getThumbnailImage();
thumbnailSource.subscribe((res) => {
defaultThumbnail.push(res);
});
formData.append('thumbnail', defaultThumail);
}
if (!articleImage) {
const articleSource = this.getArticleImage();
articleSource.subscribe((res) => {
defaultArticle.push(res);
});
formData.append('articleImage',defaultArticle);
}
formData.forEach((value, key) => {
console.log(key + ' ' + value);
});
return this.httpClient.post<IAPIResponse<IArticleCollection[]>>(`${baseUrl}/article/`, formData);
}
getThumbnailImage() {
return this.httpClient
.get('assets/images/logos/logo.png', {
responseType: 'arraybuffer',
})
.pipe(
map((response: any) => {
return new File([response], 'thumbnail-default.png', { type: 'image/png' });
}),
);
}
getArticleImage() {
return this.httpClient
.get('assets/images/logos/logo.png', {
responseType: 'arraybuffer',
})
.pipe(
map((response: any) => {
return new File([response], 'article-default.png', { type: 'image/png' });
}),
);
}
You return an observable that can depend of two observables or not, so you can use the Rxjs operators
of: return an objservable of a value, e.g. of(1) return an
Observable
forkJoin: return an observable compouned from others observables
switchmap: transform an observable in another observable (it's used
when the "inner" observable depend from the outer observable
//you create two observables
const thumbnailSource = thumbnail? of(thumbnail):this.getThumbnailImage();
const articleSource = articleImage? of (articleImage) : this.getArticleImage();
//you create an unique observable using forkJoin
return forkJoin([thumbnailSource,articleSource]).pipe(
switchMap(([thumbnail,articleImage])=>
{
const formData = new FormData();
formData.append('title', articleData.title);
formData.append('author', articleData.author);
formData.append('articleData', articleData.articleData);
formData.append('published', JSON.stringify(articleData.published));
formData.append('thumbnail', thumbnail);
formData.append('articleImage',articleImage);
return this.httpClient.post<IAPIResponse<IArticleCollection[]>>
(`${baseUrl}/article/`, formData);
}
))
BTW, is unneccesary use formData, you can use a simple object
return forkJoin([thumbnailSource,articleSource]).pipe(
switchMap(([thumbnail,articleImage])=>
{
const data={
'title', articleData.title,
'author', articleData.author,
'articleData', articleData.articleData,
'published', JSON.stringify(articleData.published),
'thumbnail', thumbnail,
'articleImage',articleImage
}
return this.httpClient.post<IAPIResponse<IArticleCollection[]>>
(`${baseUrl}/article/`, data);
}
))
I am trying to send an http.post request for each element of an array, my method works well, but when I subscribe, it does it for each of the requests, if someone could help me optimize this, I will I would really appreciate it, here I leave the snippets of my code.
component.ts
saveExclusion() {
this.indForm.value.Centers.forEach(element => {
for (const days of this.exclusionDays) {
delete days.horadesde;
delete days.horahasta;
delete days.id;
for (const key in days) {
if (days[key] === true) {
days[key] = true;
}else if (days[key] === false) {
delete days[key];
}
}
}
const valueForm = this.indForm.value;
valueForm.ResourceId = this.idResource;
valueForm.TimeZoneId = 'America/Santiago';
valueForm.CenterId = element;
this.exclusionFunc = false;
this.apiFca.saveNew(valueForm, this.exclusionDays)
.pipe(last()).subscribe((res: any) => {
console.log(res)
if (res === '200') {
this.successMessage = true;
this.exclusionDays = [];
this.indForm.reset();
this.ngOnInit();
setTimeout(() => {
this.successMessage = false;
}, 3000);
}
}, err => {
console.log('error', err);
});
});
}
service.ts
saveNew(exclusionData, daysBlock) {
let reason = '';
const dt = new Date();
const n = dt.getTimezoneOffset();
const tz = new Date(n * 1000).toISOString().substr(14, 5);
if (exclusionData.OtherReason) {
reason = exclusionData.ExclusionReason + ' ' + exclusionData.OtherReason;
} else {
reason = exclusionData.ExclusionReason;
}
if (exclusionData.ExclusionType !== 'Partial' ) {
daysBlock = [];
}
const data = {Exclusion: new ExclusionClass(
[],
reason,
exclusionData.ExclusionType,
exclusionData.Repetition,
exclusionData.CenterId,
exclusionData.ProfessionalName,
exclusionData.ResourceId,
daysBlock,
exclusionData.TimeZoneId,
'Exclude',
exclusionData.Unit,
exclusionData.ValidFrom + 'T' + exclusionData.ValidTimeFrom + ':00-' + tz,
exclusionData.ValidTo + 'T' + exclusionData.ValidTimeUntil + ':59.999-' + tz
)};
if (exclusionData.CenterId === '') {
delete data.Exclusion.CenterId;
}
return this.http
.post("url", data)
.pipe(
map((res: any) => {
return res.code;
})
);
}
greetings, and I look forward to your comments, thanks.
I'm not fully confident in my rxjs knowledge but it looks like, because of .pipe(last()), you are only watching the last request? I'd recommend you only set success if all completed without error, like
this.apiFca.saveNew(valueForm, this.exclusionDelays)
.subscribe(
res => {
console.log(res);
},
err => {
console.log(err);
},
() => {
this.successMessage = true;
// etc. etc. etc.
});
or maybe instead of using this.successMessage use something like this.saveState$ that would be the a BehaviorSubject object initialized with 'idle' (or some enum thereof) that your saveExclusion() function manages. That way, the beginning of your saveExclusion() function could
set const saveState$ = this.saveState$
assert that saveState$.getValue() === 'in process' or if not, do something about it,
saveState$.next('in process');
and you could change your subscribe line to
this.apiFca.saveNew(valueForm, this.exclusionDelays)
.subscribe(
res => {
if (res !== '200') {
saveState$.next('unexpected result')
} },
err => {
console.log(err);
saveState$.next('error');
},
() => {
if (saveState$.getValue() === 'in process') {
saveState$.next('success');
} }
);
And then you can subscribe to your component's saveState$ as well (though outside of the component you'd want to provide saveState$.asObservable() so values can't be injected by outside code). This affords some elegant event-driven code in your component initialization:
saveState$.pipe(filter(val => val === 'error'))
.subscribe(functionToTellYourUserThereWasAnError);
// if successful, we want other code to know, but immediately change it back to 'idle' even if other code errors
saveState$.pipe(filter(val => val === 'success')
.subscribe(val => saveState$.next('idle'));
// upon success, reset me
saveState$.pipe(filter(val => val === 'success'))
.subscribe(
val => {
this.exclusionDays = [];
// etc. etc.
// setTimeout not needed because set to 'idle' in a different thread.
}
)
Plus, I think your template could reflect and change the UI in response to changes in saveState$ as well, so your save button can be enabled/disabled based on whether or not saveState is 'idle', etc.
this is where i am fetching object from backend with single object.
export function downloadFileService(url: string, payload) {
return new Promise(( reject) => {
const getHeaders: object = { ...PostHeaders, body: JSON.stringify(payload) }
fetch(url, getHeaders)
.then((response) => {
if (response.ok) {
const getFileNameFromResponse = response.headers.get("Content-Disposition");
response.blob()
.then((response) => {
const fileName = getFileNameFromResponse.split('/').pop();
const type = 'application/zip';
showXLS(response, fileName, type);
});
} else {
response
.json()
.then((json) => {
const errors = json;
reject(errors ? errors.messageList : []);
});
}
});
});
}
I have created an action for the check box selected, which is just passing one object. I want to pass an array in one object.
export const downloadFormPDF = (dispatch, getState) => {
const state: IAppState = getState(dispatch);
state.payrollTaxFormReducerState.checked.forEach(item => {
const taxFormItem=state.payrollTaxFormReducerState.formCardList[item];
const downloadPdf=
{selectedCards: taxFormItem.itemID + '~' + taxFormItem.federalId + '~' + taxFormItem.formType + '~' + taxFormItem.year + '~' + taxFormItem.quarter};
// itemID:taxFormItem.itemID,
// federalId: taxFormItem.federalId,
// formType: taxFormItem.formType,
// year: taxFormItem.year,
// quarter: taxFormItem.quarter
downloadFileService('/mascsr/wfn/payrollTaxForms/metaservices/taxforms/downloadpdf', downloadPdf);
console.log("download file", downloadPdf, taxFormItem );
});
}
I have two functions in my Node.js application:
retrieveIssues: function(githubAppId, pemFilePath, repoOrg, repoName, callback) {
const octokit = require('#octokit/rest')();
let data = null;
gitInstallationAccessToken.genInstallationAccessToken(githubAppId, pemFilePath, (installationAccessToken) => {
octokit.authenticate({
type: 'app',
token: `${installationAccessToken}`
});
async function paginate(method) {
let response = await method({
q: "repo:" + repoOrg + "/" + repoName + " is:issue" + " state:open",
per_page: 100
});
data = response.data.items;
var count = 0;
while (octokit.hasNextPage(response)) {
count++;
console.log(`request n°${count}`);
response = await octokit.getNextPage(response);
data = data.concat(response.data.items);
}
return data;
}
paginate(octokit.search.issues)
.then(data => {
callback(data);
})
.catch(error => {
console.log(error);
});
});
}
retrieveEnerpriseIssues: function(repoOrg, repoName, callback) {
const octokit = require('#octokit/rest')({
baseUrl: config.githubEnterprise.baseUrl
});
let data = null;
// token auth
octokit.authenticate({
type: 'basic',
username: config.githubEnterprise.username,
password: config.githubEnterprise.token
});
async function paginate(method) {
let response = await method({
q: "repo:" + repoOrg + "/" + repoName + " is:issue" + " label:sdk" + " state:open",
per_page: 100
});
data = response.data.items;
var count = 0;
while (octokit.hasNextPage(response)) {
count++;
console.log(`request n°${count}`);
response = await octokit.getNextPage(response);
data = data.concat(response.data.items);
}
return data;
}
paginate(octokit.search.issues)
.then(data => {
callback(data);
})
.catch(error => {
console.log(error);
});
}
}
The first accesses public GitHub, the second a private Github. Whilst there are some very distinct differences(authentication type and number of parameters passed etc), they are very similar. I was wondering if these could be refactored into a single function or if that is even a good idea. If it is possible and could improve my code how is this done?
You can, and given the amount of duplication, probably should refactor. It was a little tricky without any tests and without the ability to run the code but maybe this would do the trick?
retrieve: function({repoOrg, repoName, callback, octoKitArgs, octoKitAuthArgs}) {
const octokit = require('#octokit/rest')(octoKitArgs);
let data = null;
octokit.authenticate(octoKitAuthArgs);
async function paginate(method) {
let response = await method({
q: "repo:" + repoOrg + "/" + repoName + " is:issue" + " label:sdk" + " state:open",
per_page: 100
});
data = response.data.items;
var count = 0;
while (octokit.hasNextPage(response)) {
count++;
console.log(`request n°${count}`);
response = await octokit.getNextPage(response);
data = data.concat(response.data.items);
}
return data;
}
paginate(octokit.search.issues)
.then(data => {
callback(data);
})
.catch(error => {
console.log(error);
});
}
// call as private github
retrieve({
repoOrg: "",
reportName: "",
callback: () => {},
octoKitArgs: {baseUrl: config.githubEnterprise.baseUrl},
octoKitAuthArgs: {type: 'basic', username: config.githubEnterprise.username, password: config.githubEnterprise.token},
});
// call as public github
gitInstallationAccessToken.genInstallationAccessToken(githubAppId, pemFilePath, (installationAccessToken) =>
retrieve({
repoOrg: "",
reportName: "",
callback: () => {},
octoKitArgs: undefined,
octoKitAuthArgs: {type: 'app', token: `${installationAccessToken}`},
})
);
Let me know how this looks.
I have react-native 0.44.0 and react-native-fbsdk 0.5.0. ShareDialog component work fine, but due to lack of docs explanation had been totally stuck. I have app with own API. I make API call fetch sharing template with photos array.
.then((responseData) => {
console.log("Facebook Share Api Test")
console.log(responseData)
// After receiving result checking Platform
// If this is iOS we should let our result image links be fetched to encode it in Base64.
if(Platform.OS !== 'android'){
console.log("Not Andro!d!")
let imgUrl
let sharePhotoContent
let iteratePhotos = function (data) {
var photoInfo = [];
var ready = Promise.resolve(null)
data.forEach(function (value, i) {
let iconURL = API.SERVER_URL + API.SERVICE_PORT + API.HEAD_ICON_RES_URL + value.photo_id + 'S'
ready = ready.then(function () {
return RNFetchBlob
.fetch('GET', iconURL)
.then(res => res.data)
.then(resData => {
imgUrl = 'data:image/jpeg;base64,' + resData
console.log(imgUrl)
return imgUrl
})
.then(img => {
console.log(img)
let res = {
imageUrl: img,
userGenerated: true,
caption: value.comment
}
return res
})
.catch(err => {
console.log(err)
})
}).then(function (resData) {
photoInfo[i] = resData;
});
});
return ready.then(function () { return photoInfo; });
}
iteratePhotos(responseData.photos).then((res) => {
console.log('res', res)
if(res.length > 0){
sharePhotoContent = {
contentType: 'photo',
contentDescription: 'Wow, check out this great site!',
photos: res
}
} else {
sharePhotoContent = {
contentType: 'link',
contentUrl: 'some url',
message: responseData.message
}
}
ShareDialog.canShow(sharePhotoContent)
.then((canShow) => {
if (canShow) {
return ShareDialog.show(sharePhotoContent);
}
})
.then((result) => {
this.setState({isshowIndicator: false})
if(!result.isCancelled){
this.setState({isFacebookShared: true})
setTimeout(() => alert("Success!"), 100)
}
})
.catch(error => {
this.setState({isshowIndicator: false})
console.log(error)
setTimeout(() => alert('Share fail with error: ' + error), 100)
}
)
})
} else {
let photoInfo = responseData.photos.map(value => {
return {
imageUrl: API.SERVER_URL + API.SERVICE_PORT + API.HEAD_ICON_RES_URL + value.photo_id + 'S',
...value
}
})
console.log(photoInfo, "It IS ANDROID")
if(responseData.photos.length > 0){
var sharePhotoContent = {
contentType: 'photo',
photos: photoInfo
}
} else {
var sharePhotoContent = {
contentType: 'link',
contentUrl: 'some url',
message: responseData.message
}
}
ShareDialog.canShow(sharePhotoContent)
.then((canShow) => {
if (canShow) {
return ShareDialog.show(sharePhotoContent);
}
})
.then((result) => {
this.setState({isshowIndicator: false})
if(!result.isCancelled){
this.setState({isFacebookShared: true})
setTimeout(() => alert("Success!"), 100)
}
})
.catch(error => {
this.setState({isshowIndicator: false})
setTimeout(() => alert('Share fail with error: ' + error), 100)
})
}
})
When I tap share, sharedialog opens and photos that I want are pasted but message line waits to be filled
But I need into ShareDialog opened:
Photos needed to be attached;
Message to be prefilled according that one I received from my API.
Is this possible? Please help this is prerelease feature needed to be implemented very fast and I havent any idea how((
Attaching screenshots that describes 1. what is going now here? 2. What i want to do.
some social network like facebook does not support pre-filling the message for users as seen in their Policy: https://developers.facebook.com/policy/#socialplugins