Custom XHR Callbacks Handler - javascript

I'm working on a small typescript http library for my friend to simplify the http requests a little bit. I want my friend to be able to send Asynchronous POST requests using method post() from the Http object I've created.
I want to accomplish something similar to subscribe() method in Angular 2. What I mean is I want to create a function, which would be responsible for callbacks (3 types - success, error, complete) and I would use it on my Http's post() method. Here is what I have until now.
Basically here is the written idea:
Http:
import { IHeader } from 'interfaces';
import { SubscribeAble } from 'subscribeAble';
class Http {
http: XMLHttpRequest;
constructor() {
this.http = new XMLHttpRequest;
}
post(url: string, data: Object, headers?: Array<IHeader>) {
this.http.open('POST', url);
if(headers) {
for(let header of headers) {
this.http.setRequestHeader(header.name, header.value);
}
}
this.http.send(JSON.stringify(data));
return new SubscribeAble(this.http);
}
}
SubscribeAble:
export class Subscribe {
http: XMLHttpRequest;
constructor(http) {
this.http = http;
}
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
this.http.onload = success;
if(error) { this.http.onerror = error; }
if(complete) { this.http.onreadystatechange = complete; }
}
}
What I need now is the idea of how to inject the data to functions in subscribe() method... a bit more simple: I want 'success' variable to have this.http.response value in function (success) => {}. Thank you in advance.

I finally figured out how to repair the subscribe method. I used callbacks to achieve what I wanted to. Here is the code:
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
let callback = (cb: (res) => void) {
return callback(this.http.response);
}
this.http.onload = () => {
return callback(success);
}
if(error) {
this.http.onerror = () => {
return callback(error);
}
}
if(complete) { this.http.onloadend = complete; }
}

I think you can do something like this:
subscribe(success: (success) => void, error?: (error) => void, complete?: () => void) {
this.success = success;
this.error = error;
this.complete = complete;
this.http.onload = this.onload;
if(error) { this.http.onerror = this.onerror; }
if(complete) { this.http.onreadystatechange = this.oncomplete; }
}
onload() {
if (this.http.status === 200) {
this.success(this.response);
} else {
if (this.error)
this.error(this.http.statusText);
}
}
}
you set the functions the user send you in subscribe as class variables, and call them with the data you want to send as their parameters.
and you can create the onerror and oncomplete method for the other 2 functions

Related

How to make sequential service call on success of first service response in Angular

I need to make multiple service call in angular one after other. need to pass the first
service call respose as input to another service.
Here is my component:
Demo(): any {
if (fileToUpload) {
this._voiceboxService.upload(fileToUpload)
.subscribe((res: any) => {
this.text=res.prediction
console.log(res);
});
}
else
console.log("FileToUpload was null or undefined.");
}
}
Here is my Service: i need to call all three service on success of one service and need to
pass first service resposnse as input for next service
upload(fileToUpload: any) {
let input = new FormData();
input.append("file", fileToUpload);
return this.http.post<any>('https://localhost:5001/', input)
language(data: any) {
return this.http.post<any>('https://localhost:5002', data)
}
getDetails(data: any) {
return this.http.post<any>('https://localhost:5003', data)
}
Use mergeMap.
I assume you want to do this in your component:
this._voiceboxService.upload(fileToUpload).pipe(mergeMap(upload =>
this._voiceboxService.language(upload)
.pipe(mergeMap(language => this._voiceboxService.getDetails(language))
))).subscribe((res: any) => {
this.text=res.prediction
console.log(res);
});
You can use map in the end organize your final value result.
You could use any of the RxJS higher order mapping operators like switchMap to map from one observable to another. You could find differences between different mapping operators here.
Service
upload(fileToUpload: any) {
let input = new FormData();
input.append("file", fileToUpload);
return this.http.post<any>('https://localhost:5001/', input).pipe(
switchMap(res => this.language(res)), // <-- `res` = response from previous request
switchMap(res => this.getDetails(res)) // <-- `res` = response from `this.language()`
);
}
language(data: any) {
return this.http.post<any>('https://localhost:5002', data)
}
getDetails(data: any) {
return this.http.post<any>('https://localhost:5003', data)
}
Component
Demo(): any {
if (fileToUpload) {
this._voiceboxService.upload(fileToUpload).subscribe({
next: (res: any) => { // <-- `res` = response from `getDetails()`
this.text = res.prediction
console.log(res);
},
error: (error: any) => {
// handle errors
}
});
} else {
console.log("FileToUpload was null or undefined.");
}
}

How to show spinner only if data are fetched from Http service?

I have to show a spinner only during http service call, and dismiss it when my component receives data.
I wrote a little cache service in order to fetch data from http service only the first time, and load that data from the cache during every other call, avoiding to call another time the http service.
The service is working as expected,but what if I'd like to show the spinner only during the http call and not when data are fetched from cache?
This is my component's code, it works when getReviewsCategory(this.id) method of my service calls http service, but when it fetches from cache the spinner is never dismissed.
Data are loaded in correct way in the background, but the spinner keeps going.
presentLoading() method is in ngOnInit so it's called everytime, what if I want to call it only when data are fetched from cache? How my component could know it?
ngOnInit() {
this.presentLoading();
this.CategoryCtrl();
}
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
});
}
async presentLoading() {
const loadingController = this.loadingController;
const loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingController.dismiss();
}
}
EDIT1: this is the CacheService:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CachingService {
constructor() { }
private _cache = {};
isCashed(url: string) {
return this._cache[url];
}
getData(url: string) {
return this._cache[url];
}
setData(url) {
return (data) => {
if (data && (data instanceof Error) === false) {
this._cache[url] = data;
};
}
}
reset() {
this._cache = {};
}
}
And this is the server service's method:
getReviewsCategory(cat_id) : Observable<any> {
if (this._c.isCashed(url)) {
return of(this._c.getData(url));
}else{
var modeapp = window.sessionStorage.modeapp;
var typemodeapp = typeof(window.sessionStorage.modeapp);
if (modeapp === "online") {
let promise = new Promise ((resolve, reject) => {
this.httpNative.get(url, {}, {}).
then((data) => {
let mydata = JSON.parse(data.data);
console.log("Data from HTTP: ");
console.log(mydata);
resolve(mydata);
}, (error) => {
console.log("error in HTTP");
reject(error.error);
}
);
});
var observable = from(promise);
}
}
return observable
.pipe(
tap(this._c.setData(url))
);
I can see you're returning an observable from the service, you can try the following to see if this helps.
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
},
(error) => console.log(error),
() => this.stopLoading(); // This always execute
);}
Docs: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-subscribe
However, I believe the problem may come from the object you're calling .dismiss()
from. You should be calling dismiss on the instance of the element and not the object itself.
let loadingElement: Loading = null;
async presentLoading() {
const loadingController = this.loadingController;
this.loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingElement.dismiss();
}
You can use an HttpInterceptor class to intercept all http calls, and in the intercept method, you can stop and start a spinner.
Broadly speaking, the structure is:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Start the spinner.
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// Stop the spinner
}
return event;
})
);

Inconsitant mergeMap behaviour

I am currently working on a file uploading method which requires me to limit the number of concurrent requests coming through.
I've begun by writing a prototype to how it should be handled
const items = Array.from({ length: 50 }).map((_, n) => n);
from(items)
.pipe(
mergeMap(n => {
return of(n).pipe(delay(2000));
}, 5)
)
.subscribe(n => {
console.log(n);
});
And it did work, however as soon as I swapped out the of with the actual call. It only processes one chunk, so let's say 5 out of 20 files
from(files)
.pipe(mergeMap(handleFile, 5))
.subscribe(console.log);
The handleFile function returns a call to my custom ajax implementation
import { Observable, Subscriber } from 'rxjs';
import axios from 'axios';
const { CancelToken } = axios;
class AjaxSubscriber extends Subscriber {
constructor(destination, settings) {
super(destination);
this.send(settings);
}
send(settings) {
const cancelToken = new CancelToken(cancel => {
// An executor function receives a cancel function as a parameter
this.cancel = cancel;
});
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.next([null, resp.data]))
.catch(e => this.next([e, null]));
}
next(config) {
this.done = true;
const { destination } = this;
destination.next(config);
}
unsubscribe() {
if (this.cancel) {
this.cancel();
}
super.unsubscribe();
}
}
export class AjaxObservable extends Observable {
static create(settings) {
return new AjaxObservable(settings);
}
constructor(settings) {
super();
this.settings = settings;
}
_subscribe(subscriber) {
return new AjaxSubscriber(subscriber, this.settings);
}
}
So it looks something like this like
function handleFile() {
return AjaxObservable.create({
url: "https://jsonplaceholder.typicode.com/todos/1"
});
}
CodeSandbox
If I remove the concurrency parameter from the merge map function everything works fine, but it uploads all files all at once. Is there any way to fix this?
Turns out the problem was me not calling complete() method inside AjaxSubscriber, so I modified the code to:
pass(response) {
this.next(response);
this.complete();
}
And from axios call:
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.pass([null, resp.data]))
.catch(e => this.pass([e, null]));

How to properly remove multiple event listeners?

(Obviously this doesn't have much to do with typescript, except that the example code is in ts).
import { createWriteStream, WriteStream } from "fs";
export class Util {
public static openWrite(path: string): Promise<WriteStream> {
return new Promise<WriteStream>((resolve, reject) => {
const result = createWriteStream(path);
const onError = (err: Error) => {
// How to remove both listeners here?
reject(err);
}
const onOpen = (fd: number) => {
// How to remove both listeners here?
resolve(result);
};
result.on("error", onError);
result.on("open", onOpen);
});
}
}
The code should say it all. I have a hard time to see how the function should be written such that it handles both the success and failure scenarios correctly, while ensuring that all added event handlers are removed when everything is done.
Of course, there is always the possibility to call removeAllListeners, but that looks like a hack to me.
I think this is a good use-case for finally.
let onError, onOpen;
const result = createWriteStream(path);
return new Promise<WriteStream>((resolve, reject) => {
onError = (err: Error) => {
// How to remove both listeners here?
reject(err);
}
onOpen = (fd: number) => {
// How to remove both listeners here?
resolve(result);
};
result.on("error", onError);
result.on("open", onOpen);
}).finally(() => {
result.removeListener("error", onError);
result.removeListener("open", onOpen);
});
Or if you know that no other listeners are already attached to the EventEmitter you can simplify it as follows:
const result = createWriteStream(path);
return new Promise<WriteStream>((resolve, reject) => {
result.on("error", reject);
result.on("open", resolve);
}).then(() => {
return result;
}).finally(() => {
result.removeAllListeners("error");
result.removeAllListeners("open");
});
In NodeJS, all Stream is an EventEmitter (https://nodejs.org/api/stream.html#stream_stream)
The EventEmitter has a method called removeListener. So try to do the Following:
return new Promise<WriteStream>((resolve, reject) => {
const result = createWriteStream(path);
const onError = (err: Error) => {
result.removeAllListeners()
reject(err);
}
const onOpen = (fd: number) => {
result.removeAllListeners()
resolve(result);
}
result.on("error", onError);
result.on("open", onOpen);
});
Here's what I finally went with ...
public static async openWrite(path: string): Promise<WriteStream> {
const factory = new StreamFactory(() => createWriteStream(path));
try {
return await factory.get();
} finally {
factory.dispose();
}
}
... with StreamFactory defined as follows:
class StreamFactory<T extends EventEmitter> {
private stream: T;
private onOpen: (fd: number) => void;
private onError: (err: Error) => void;
private readonly promise: Promise<T>;
public constructor(create: () => T) {
this.promise = new Promise<T>((resolve, reject) => {
this.stream = create();
this.onOpen = fd => resolve(this.stream);
this.onError = err => reject(err);
this.stream.on("open", this.onOpen).on("error", this.onError);
});
}
public get(): Promise<T> { return this.promise; }
public dispose(): void {
this.stream.removeListener("open", this.onOpen).removeListener("error", this.onError);
}
}
I've tested the failure and success paths of the above and the event handlers are removed correctly in both cases. Of course, this is just a variation of Jakes answer, so +1 to him for pointing this out.
This has the advantage of not resorting to Promise.finally, which doesn't seem to be available on my platform (Node 8.x).
It appears that it is definitely not a good idea to use removeAllListeners(). At least on my platform, the library itself seems to add a listener for "open" when an error occurs. removeAllListeners() would remove that with possibly unintended consequences.

Testing HttpClient call with callFake()

I am trying to create a spec to test a method in my Angular service that makes a GET request. The difficulty I am having is mocking the method to get it to return an error instead of the response. If I cannot get it to return an error (such a 400 or 500 for example) I cannot provide full code coverage...
Code being tested:
maingrid.service.ts:
async loadAccountListPromise(id: string) {
let queryParams = `?emailAddress=${id}`;
let promise = new Promise((resolve, reject) => {
this.http.get(`${this.baseUrl}` + queryParams, { responseType: 'json' })
.toPromise()
.then(
(data) => {
this.results = this.formatData(data);
resolve(this.results);
},
(err) => {
this.logService.error('loadAccountListPromise() exception:', err);
this.setError(this.message[0], err);
reject('loadAccountListPromise() exception');
}
);
});
return promise;
}
setError(errorMessage: string, errorCode?: string): void {
this._error.next(new NxpError(errorMessage, 'AccountListService',
errorCode));
}
clearError(): void {
this._error.next(null);
}
This is the spec I have attempted to write to mock the method using callFake():
maingrid.service.spec.ts
it('logs and sets a local error for system errors/exceptions', () => {
let id: string = 'ppandya#pershing.com';
let myUrl = 'https://localhost:9999/...';
let queryParams = `?emailAddress=${id}`;
spyOn(httpClient, 'get').and.callFake( loadAccountListPromise( (response) => {
// need to return error here...somehow
}));
spyOn(logService, 'error');
spyOn(maingridService, 'setError');
maingridService.loadAccountListPromise(id);
let request = httpMock.expectOne(myUrl + queryParams);
expect(request.request.method).toEqual('GET');
httpMock.verify();
expect(logService.error).toHaveBeenCalled();
expect(maingridService.setError).toHaveBeenCalled();
});
I am not sure what I need to do to properly mock the loadAcountListPromise() method so that it enters the error block and calls the setError() and logService.error() methods.
Try to use the 'spyOn()' and return a throw like this:
spyOn(httpClient, 'get').and.returnValue(Observable.throw({status: 404}));
//Observable.throw(new Error(`Error: ${error}`));

Categories