Can you tell me why this () success method is not firing? When I use forkjoin() it is working nicely.Success method must fire every time no? Hope it is like a final method on try-catch block.
Note: Please see the inline comments too.
.ts
getAllBooksReadAndUnRead(id: number, loader?) {
this.article.getAllBooksReadAndUnRead(id)
.map((res: any) => res.json())
.subscribe(res => {
this.setAllData(res); //it comes to here
this.loadingControllerService.dismissLoader(loader);//I have to put this here.Then no problem.But I think it is duplicate of work???
},
error => {this.loadingControllerService.dismissLoader(loader);},
() => {this.loadingControllerService.dismissLoader(loader);}//not fire
});
}
Api call
getAllBooksReadAndUnRead(bookId) {
return this.apiSer.get(`${config.fromThisBook}?page=all&book[]=${bookId}`);
}
Generic method:
get(api) {
return new Observable(observer => {
let header = new Headers();
this.createHeader(header)
.then(() => {
let options = new BaseRequestOptions();
options.withCredentials = true;
options.headers = header;
this.http.get(api, options)
.subscribe(response => {
observer.next(response);
}, (e) => {
observer.error(e);
});
})
})
}
post()
post(url, params): Observable<any> {
return new Observable(observer => {
let header = new Headers();
this.createHeader(header)
.then(() => {
let options = new RequestOptions({ headers: header });
this.http.post(url, params, options)
.subscribe(response => {
observer.next(response);
observer.complete();
}, (e) => {
observer.error(e);
});
})
})
}
The problem is that you never trigger complete in your custom producer. You need something like this:
get(api) {
return new Observable(observer => {
let header = new Headers();
this.createHeader(header)
.then(() => {
let options = new BaseRequestOptions();
options.withCredentials = true;
options.headers = header;
this.http.get(api, options).subscribe(
response => observer.next(response),
(e) => observer.error(e),
() => observer.complete(); <-------------------------
);
})
})
}
Also I think you don't need a custom producer, try like this:
get(api) {
return Observable.from(this.createHeader()).map((header) => {
let options = new BaseRequestOptions();
options.withCredentials = true;
options.headers = header;
return this.http.get(api, options);
}).mergeAll();
}
Here is how it works:
Observable.from(this.createHeader()) returns an observable that will deliver values once the promise is resolved
.map((header) => { observes the value that comes from returned promise and makes an HTTP request and returns the result in the form of the observable
mergeAll() - since the previous operation returns and observable, we need to flatten it
I try to avoid wrapping code blocks in parentheses unless I have to, as it makes it easier to confuse their placement. For example, you have an extra closing } in your second to last line that may be causing an issue:
getAllBooksReadAndUnRead(id: number, loader?) {
this.article.getAllBooksReadAndUnRead(id)
.map((res: any) => res.json())
.subscribe(res => {
this.setAllData(res);//it comes to here
},
error => {this.loadingControllerService.dismissLoader(loader);},
() => {this.loadingControllerService.dismissLoader(loader);}//not fire
);
}
I would be inclined to simplify the parentheses like this:
getAllBooksReadAndUnRead(id: number, loader?) {
this.article.getAllBooksReadAndUnRead(id)
.map(res: any => res.json())
.subscribe(res => {
this.setAllData(res);//it comes to here
},
error => this.loadingControllerService.dismissLoader(loader),
() => this.loadingControllerService.dismissLoader(loader)//not fire
)};
Related
Below you can see my first attempt at creating a service-worker and the problem with this code is that it never returns cached response since cache.match(request) in addUrlToCache function is always returning undefined. Does anyone have any ideas as to why it's not finding cached requests?
import API from 'top-secret'
const PHOTOS_CACHE = 'photos-cache'
const OBJECTS_CACHE = 'objects-cache'
const urlCacheData = [
{
cacheKey: OBJECTS_CACHE,
url: API.apiUrlGetObjects
},
{
cacheKey: PHOTOS_CACHE,
url: API.apiUrlGetPhotos
}
]
function addUrlToCache (request, cacheKey) {
return caches
.open(cacheKey)
.then(cache => cache.match(request))
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse
}
return fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response))
return response.clone()
})
})
}
function clearCache () {
return caches.keys().then(cacheNames => {
const promisesToDeleteCache = cacheNames.map(cacheName =>
caches.delete(cacheName)
)
return Promise.all(promisesToDeleteCache)
})
}
self.addEventListener('activate', event => {
event.waitUntil(clearCache())
})
self.addEventListener('fetch', event => {
const urlToCache = urlCacheData.find(item =>
event.request.url.includes(item.url)
)
if (urlToCache) {
event.respondWith(
addUrlToCache(event.request, urlToCache.cacheKey)
)
}
})
After you fetch request in your addUrltoCache function replace
fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response))
return response.clone()
})
with,
return fetch(request).then(response => {
caches.open(cacheKey).then(cache => cache.put(request, response.clone()))
return response;
})
because you should clone first and then return the response.In your code you have already used your response to put value in cache.
I've just figured out that the problem was that I had JSONP requests with random callback values like bla-bla/api?callback=jsonp_randomNumber, so url would be different every time I make a request because of the random number thing, that's why cache.match check wouldn't work.
I fixed it by hardcoding callback value in the config of the jsonp library that I used (jsonp-fetch in my case).
Due to the api of a plugin I'm using not working properly. I need to merge the two different requests. I am using the thunk below.
I can get a response but I cannot seem to check for response.ok, and return the combined data:
export function fetchCategories() {
const firstPage =
"http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page=1";
const secondPage =
"http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page=2";
return dispatch => {
dispatch(isLoading(true));
Promise.all([fetch(firstPage), fetch(secondPage)])
.then(response => {
// check for ok here
response.ForEach(response => {
if (!response.ok) throw Error(response.statusText);
});
dispatch(isLoading(false));
return response;
})
.then(response => response.json())
// dispatch combined data here
.then(data => dispatch(fetchSuccessCategories(data)))
.catch(() => dispatch(hasErrored(true)));
};
}
Any ideas?
You are doing the check for .ok fine because it's in a loop, but your response is actually an array of two Response objects, it does not have a .json() method. You could do Promise.all(responses.map(r => r.json())), but I would recommend to write a helper function that does the complete promise chaining for one request and then call that twice:
function fetchPage(num) {
const url = "http://wordpress.rguc.co.uk/index.php/wp-json/tribe/events/v1/categories?per_page=60&page="+num;
return fetch(url).then(response => {
if (!response.ok)
throw new Error(response.statusText);
return response.json();
});
}
export function fetchCategories() {
return dispatch => {
dispatch(isLoading(true));
Promise.all([fetchPage(1), fetchPage(2)]).then(data => {
dispatch(isLoading(false));
dispatch(fetchSuccessCategories(merge(data)));
}, err => {
dispatch(isLoading(false));
dispatch(hasErrored(true));
});
};
}
i have one service that get data , and i called it 5 times with different parametes to get different data.
I called a function to execute in success case : it work fine.
but in case of failure one from the 5 calls i need to do something else what's not happen : it always enter in success function.
I'm using ionic 4 angular 2
this is the service i have :
public getdataLookUps(type, lcid): Promise<string[]> {
return new Promise((resolve, reject) => {
if (this.data[type + lcid]) {
resolve(this.data[type + lcid]);
return;
}
this.authService.getToken().then(
(accessToken) => {
let headers = new Headers({'Authorization': 'bearer ' + accessToken});
let url = 'error url to test failure case';
this.http.get(url, {headers: headers})
.map(res => res.json())
.toPromise()
.then(
(res) => {
this.data[type + lcid] = res;
resolve(res);
},
(error) => {
reject(error);
}
);
}
);
});
}
then I wrapper the function that calls the service like this: ( repeated 5 times with different params):
public getAreas() {
return this.lookupsService.getdataLookUps('Region', this.lcid).then(
(res) => {
this.areas = res;
},
() => {
//todo
return Promise.reject('rejection error');
}
);
}
then I call the 5 functions :
ngOnInit() {
this.getCaseCategories();
this.getAreas();
this.getWeather();
this.getMifonProjects();
this.getUserInfo();
}
and I do promise.all() here :
ngAfterViewInit(){
Promise.all(
[
this.getCaseCategories(),
this.getAreas(),
this.getWeather(),
this.getMifonProjects(),
this.getUserInfo(),
]
).then(
() => {
this.loadMap();
},
() => {
this.showErrorMessage = true;
}
);
}
This code has two callbacks for then, a success handler, and an error handler. If the code is as you have shown the error handler returns a success result so your Promise.all() will always succeed:
public getAreas() {
return this.lookupsService.getdataLookUps('Region', this.lcid).then(
(res) => {
this.areas = res;
},
() => {
//todo
}
);
}
Don't add an error handler unless you are really able to handle the error here. Instead just let the error propagate out to the next handler:
public getAreas() {
return this.lookupsService.getdataLookUps('Region', this.lcid)
.then(res => this.areas = res);
}
Now your Promise.all will give you an error when the data lookup fails.
Also stop nesting your promise handlers:
public getdataLookUps(type, lcid): Promise<string[]> {
if (this.data[type + lcid]) return Promise.resolve(this.data[type + lcid]);
return this.authService.getToken().then(
(accessToken) => {
let headers = new Headers({'Authorization': 'bearer ' + accessToken});
let url = 'error url to test failure case';
return this.http.get(url, {headers: headers})
.map(res => res.json())
.toPromise();
})
.then((res) => this.data[type + lcid] = res);
}
Once you have a Promise just return the Promise there is no need to create a new Promise. And if your promise success handler creates another promise return that to avoid nesting. Your error handler did nothing but propagate the error, so when you don't have the nested promise you don't need that either, just let the error propagate naturally.
I solved it by removing the calls of the functions in ngOnInit();
and keep everything same as my example above (not change anything in the getDataLookUps service)
Goal: To make a series of synchronous http requests and be able to subscribe to them as one observable stream.
Sample (Not Working):
let query_arr = ['test1','test2','test3']
function make_request(query_arr){
if (query_arr.length){
let payload = JSON.stringify(query_arr[0]);
let headers = new Headers();
query_arr.splice(0,1);
this.http.post('https://endpoint/post',payload,{headers:headers})
.map((res:Response) => {make_request(query_arr)})
}
}.subscribe(
data => console.log('finished http request, moving on to next http request'),
err => console.error(err),
() => console.log('all http requests have been finished')
);
make_request(query_arr)
Goal Functionality:
Need to know when each response was returned
Must know when all responses have returned
You need to leverage the flatMap operator to execute your requests in series (one after one). For this, you need to build your data processing chain recursively. The point here is to call the operator on the previous observable (the one returned by the previous request).
This way the request will wait for the previous one to be complete before executed itself. The callback provided when subscribing will be called when all requests were executed.
Here is a sample implementation of this approach:
makeRequest(queryArr, previousObservable){
if (queryArr.length) {
let payload = JSON.stringify(queryArr[0]);
let headers = new Headers();
(...)
queryArr.splice(0,1);
var observable = null;
if (previousObservable) {
observable = previousObservable.flatMap(() => {
return this.http.post('https://testsoapi.apispark.net/v1/entities', payload,{
headers:headers
})
.map((res:Response) => res.json())
.do(() => {
console.log('request finished');
});
});
} else {
observable = this.http.post('https://testsoapi.apispark.net/v1/entities', payload, {
headers:headers
})
.map((res:Response) => res.json())
.do(() => {
console.log('request finished');
});
}
return this.makeRequest(queryArr, observable);
} else {
return previousObservable;
}
}
This method can be called initially like this:
test() {
let queryArr = [
{ val: 'test1' },
{ val: 'test2' },
{ val: 'test3' }
];
this.makeRequest(queryArr).subscribe(
() => {
console.log('all requests finished');
});
}
See this plunkr: https://plnkr.co/edit/adtWwckvhwXJgPDgCurQ?p=preview.
There were a couple syntactical errors in your code as well that would need to be addressed. But those aside you can simplify greatly by using concatMap + defer instead.
let query_arr = ['test1','test2','test3'];
let self = this;
Rx.Observable.from(query_arr).map(JSON.stringify)
.concatMap(payload => {
let headers = new Headers();
return Rx.Observable.defer(() => {
self.http.post('https://endpoint/post',payload,{headers:headers});
});
}, resp => resp.json())
.subscribe(
data => console.log('finished http request, moving on to next http request'),
err => console.error(err),
() => console.log('all http requests have been finished')
);
The basic idea of this is that it will convert the query array into an Observable then it will eagerly create a series of lazy requests that will only be executed when they are subscribed to. However, by wrapping the post in a defer each request will only get dispatched when the previous one completes.
Or a non recursive version in typescript where you give an array to forkjoin
in the return observableObj(res.json()) you know each response when it returns from the httpcall
in the subscribe you know when all responses returned and an array of values
const observableObj = (obj) => Observable.of(obj)
class Requests {
private query_arr = ['test1','test2','test3']
private url = 'https://testsoapi.apispark.net/v1/entities'
public make() {
this.processHttp().subscribe(
(d) => {
console.log(d)
},
(e) => {
console.log(e)
},
() => {
console.log("http calls are done")
})
}
private httpCall(options : RequestOptions) : Observable<Response> {
let username : string = 'xxx'
let password : string = 'yyy'
let headers = new Headers()
headers.append("Authorization", "Basic " + btoa(username + ":" + password))
headers.append("Content-Type", "application/x-www-form-urlencoded")
options.headers = headers
return this.http.get(this.url,options)
}
private createRequestOptions(option1 : string) {
let data = {'option1':option1}
let params = new URLSearchParams()
for(var key in data) {
params.set(key, data[key])
}
let options = new RequestOptions({
search: params
})
return options
}
private processHttp() {
return Observable.forkJoin(
this.query_arr.map(option => {
return this.httpCall(createRequestOption(option)).flatMap((res: Response) => {
return observableObj(res.json())
})
}))
}
}
I'm building an app to get some events from facebook, take a look:
EventComponent:
events: Object[] = [];
constructor(private eventService: EventService) {
this.eventService.getAll()
.subscribe(events => this.events = events)
}
EventService:
getAll() {
const accessToken = 'xxxxxxxxxxx';
const batch = [{...},{...},{...},...];
const body = `access_token=${accessToken}&batch=${JSON.stringify(batch)}`;
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json())
}
AuthenticationService:
getAccessToken() {
return new Promise((resolve: (response: any) => void, reject: (error: any) => void) => {
facebookConnectPlugin.getAccessToken(
token => resolve(token),
error => reject(error)
);
});
}
I have a few questions:
1) How can I set an interval to update the events every 60 seconds?
2) The value of accessToken will actually come from a promise, should I do something like this?
getAll() {
const batch = [{...},{...},{...},...];
this.authenticationService.getAccessToken().then(
accessToken => {
const body = `access_token=${accessToken}&batch=${JSON.stringify(batch)}`;
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json())
},
error => {}
);
}
3) If yes, how can I also handle errors from the getAccessToken() promise since I'm returning just the Observer?
4) The response from the post request will not return an array of objects by default, I'll have to make some manipulation. Should I do something like this?
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json())
.map(response => {
const events: Object[] = [];
// Manipulate response and push to events...
return events;
})
Here are the answers to your questions:
1) You can leverage the interval function of observables:
getAll() {
const accessToken = 'xxxxxxxxxxx';
const batch = [{...},{...},{...},...];
const body = `access_token=${accessToken}&batch=${JSON.stringify(batch)}`;
return Observable.interval(60000).flatMap(() => {
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json());
});
}
2) You could leverage at this level the fromPromise function of observables:
getAll() {
const batch = [{...},{...},{...},...];
return Observable.fromPromise(this.authenticationService.getAccessToken())
.flatMap(accessToken => {
const body = `access_token=${accessToken}&batch=${JSON.stringify(batch)}`;
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json())
});
}
3) You can leverage the catch operator to handle errors:
getAll() {
const batch = [{...},{...},{...},...];
return Observable.fromPromise(this.authenticationService.getAccessToken())
.catch(() => Observable.of({})) // <-----
.flatMap(accessToken => {
const body = `access_token=${accessToken}&batch=${JSON.stringify(batch)}`;
return this.http.post('https://graph.facebook.com', body)
.retry(3)
.map(response => response.json())
});
}
In this case, when an error occurs to get the access token, an empty object is provided to build the POST request.
4) Yes sure! The map operator allows you to return what you want...
Put the event inside a timeout block and set the interval of 60s. setTimeout(() => {},60000).
Using Template string is totally fine but you're telling its value comes from a promise. If your whole block of code is inside resolve function of promise this should fine. So it depends on where your code is. And why promises .In A2 it's recommended to use Observables and not promises. Don't mix them.
You're not returning anything in the error function. So if you return error from that block you'll get error data in case of error. error => erroror error => { return error; }.
Exactly you should you map to get the response and manipulate it and return just the array from that function. .map(resp => { return resp.array}). Since respons is in JSON format now you have to get array from it and return it. you can do as much modifications you want before returning it.
Feel free to edit the answer...