Angular/RxJS Observable piping from multiple functions - javascript

So I have a http get function defined in a Service class that gets an object from an endpoint and does some processing on the result:
public get(url: string, params: {}): Observable<Object> {
return this.http.get<IResult>(this.endpointRootUrl() + url, {
params: params
}).pipe(
map(res => {
if (res.Result !== 0)
throw new Error(res.Message)
else
return res.Object;
}),
catchError((err: HttpErrorResponse) => throwError(err.statusText))
)
}
This function is called from an ExampleService which gets the observable and does some more processing on it:
public loadData(): Observable<IData[]> {
return this.get("/DataLink/ListData", {}).pipe(
map(res => { return <IData[]>res }),
catchError((err: string) => throwError("There was an error retrieving data: " + err))
)
}
My questions are:
In the http get function where I check the Result property of the IResult object that gets returned from the back-end, I throw an error if the value of the result is not what is expected.
The issue is, the Message property does not get properly sent into the catchError part of the loadData function; the error message returns as "There was an error retrieving data: undefined". What am I doing wrong here?
Is this code an acceptable way to achieve what I'm trying to do? I'm open to suggestions/critique.
Thanks in advance for any help offered.

Try to move catchError before all another pipes, exept takeUntil
e.g.
Inside component:
return this.apiService.changeStatus(id, status)
.pipe(
catchError((error: HttpErrorResponse) => {
this.showMsg('error', error.error.message);
return throwError(error);
}),
tap((data: SomeResponseModel) => {
this.status = data;
})
);
Inside service
changeStatus(id: number, status: StatusesEnum): Observable<SomeModel> {
const payload: { status: StatusesEnum} = { status };
return this.http.patch<ResponseModel<SomeModel>>(this.apiUrl(`/${id}/status`), payload)
.pipe(map((data: ResponseModel<SomeModel>) => new SomeModel(data.data)));
}

In the http get function where I check the Result property of the
IResult object that gets returned from the back-end, I throw an error
if the value of the result is not what is expected. The issue is, the
Message property does not get properly sent into the catchError part
of the loadData function; the error message returns as "There was an
error retrieving data: undefined". What am I doing wrong here?
Error object doesnt have statusText property
I think you are looking for message property
catchError((err: Error) => throwError(err.message))
With that change rest of code works fine.
Example
onst data = {
Message: "Random error",
Result: 1,
Object: {
test: 1
}
}
function get(data) {
return rxjs.of(data).pipe(
rxjs.operators.map(res => {
if (res.Result !== 0) {
throw new Error(res.Message)
} else {
return res.Object;
}
}),
rxjs.operators.catchError((err) => {
return rxjs.throwError(err.message)
})
)
}
function loadData(data) {
return get(data).pipe(
rxjs.operators.catchError(err => {
return rxjs.throwError(`There was an error retrieving data: "${err}"`)
})
)
}
loadData(data).subscribe({
next: (value) => {
console.log(value)
},
error: (error) => {
console.log('error ', error)
},
complete: () => {
console.log('completed ')
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>

Related

Determine the generic type of promise [duplicate]

I am using window.fetch in Typescript, but I cannot cast the response directly to my custom type:
I am hacking my way around this by casting the Promise result to an intermediate 'any' variable.
What would be the correct method to do this?
import { Actor } from './models/actor';
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then(res => {
// this is not allowed
// let a:Actor = <Actor>res;
// I use an intermediate variable a to get around this...
let a:any = res;
let b:Actor = <Actor>a;
})
A few examples follow, going from basic through to adding transformations after the request and/or error handling:
Basic:
// Implementation code where T is the returned data shape
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<T>()
})
}
// Consumer
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Data transformations:
Often you may need to do some tweaks to the data before its passed to the consumer, for example, unwrapping a top level data attribute. This is straight forward:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => { /* <-- data inferred as { data: T }*/
return data.data
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Error handling:
I'd argue that you shouldn't be directly error catching directly within this service, instead, just allowing it to bubble, but if you need to, you can do the following:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json<{ data: T }>()
})
.then(data => {
return data.data
})
.catch((error: Error) => {
externalErrorLogging.error(error) /* <-- made up logging service */
throw error /* <-- rethrow the error so consumer can still catch it */
})
}
// Consumer - consumer remains the same
api<{ title: string; message: string }>('v1/posts/1')
.then(({ title, message }) => {
console.log(title, message)
})
.catch(error => {
/* show error message */
})
Edit
There has been some changes since writing this answer a while ago. As mentioned in the comments, response.json<T> is no longer valid. Not sure, couldn't find where it was removed.
For later releases, you can do:
// Standard variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<T>
})
}
// For the "unwrapping" variation
function api<T>(url: string): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json() as Promise<{ data: T }>
})
.then(data => {
return data.data
})
}
If you take a look at #types/node-fetch you will see the body definition
export class Body {
bodyUsed: boolean;
body: NodeJS.ReadableStream;
json(): Promise<any>;
json<T>(): Promise<T>;
text(): Promise<string>;
buffer(): Promise<Buffer>;
}
That means that you could use generics in order to achieve what you want. I didn't test this code, but it would looks something like this:
import { Actor } from './models/actor';
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json<Actor>())
.then(res => {
let b:Actor = res;
});
Actually, pretty much anywhere in typescript, passing a value to a function with a specified type will work as desired as long as the type being passed is compatible.
That being said, the following works...
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then((res: Actor) => {
// res is now an Actor
});
I wanted to wrap all of my http calls in a reusable class - which means I needed some way for the client to process the response in its desired form. To support this, I accept a callback lambda as a parameter to my wrapper method. The lambda declaration accepts an any type as shown here...
callBack: (response: any) => void
But in use the caller can pass a lambda that specifies the desired return type. I modified my code from above like this...
fetch(`http://swapi.co/api/people/1/`)
.then(res => res.json())
.then(res => {
if (callback) {
callback(res); // Client receives the response as desired type.
}
});
So that a client can call it with a callback like...
(response: IApigeeResponse) => {
// Process response as an IApigeeResponse
}
This is specifically written for POST request. That is why it has "variables" parameter. In case of "GET" request same code will work, vriables can be optional is handled
export type FetcherOptions = {
queryString: string
variables?: FetcherVariables
}
export type FetcherVariables = {[key: string]: string | any | undefined}
export type FetcherResults<T> = {
data: T
}
const fetcher = async <T>({queryString,
variables }: FetcherOptions): Promise<FetcherResults<T>> => {
const res = await fetch(API_URL!, {
method: "POST",
headers: {
"Content-Type": "application/json",
// You can add more headers
},
body: JSON.stringify({
queryString,
variables
})
})
const { data, errors} = await res.json()
if (errors) {
// if errors.message null or undefined returns the custom error
throw new Error(errors.message ?? "Custom Error" )
}
return { data }
}
For this particular use-case:
"Fetching data from a remote resource, we do not have control and want to validate filter before injecting in our current application"
I feel recommending zod npm package
https://www.npmjs.com/package/zod
with the following fashion:
// 1. Define a schema
const Data = z.object({
// subset of real full type
name: z.string(),
// unExpectedAttr: z.number(), --> enabling this will throw ZodError
height: z.string(),
mass: z.string(),
films: z.array(z.string()),
});
// 2. Infer a type from the schema to annotate the final obj
type DataType = z.infer<typeof Data>;
(async () => {
try {
const r = await fetch(`https://swapi.dev/api/people/1/?format=json`);
const obj: DataType = Data.parse(await r.json());
console.log(obj); // filtered with expected field in Data Schema
/**
Will log:
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
films: [
'https://swapi.dev/api/films/1/',
'https://swapi.dev/api/films/2/',
'https://swapi.dev/api/films/3/',
'https://swapi.dev/api/films/6/'
]
}
*/
} catch (error) {
if (error instanceof ZodError) {
// Unexpected type in response not matching Data Schema
} else {
// general unexpected error
}
}
})();

Promise.reject() returns its value differently from Promise.resolve()?

I've had a hard time figuring out how Promise.reject() returns its value.
const propertyData = {
id: 1,
propertyName: '1BR Japanese-style Private Room near Kyoto Station'
}
function handleClick(e) {
getData()
.then(data => {
console.log(data.id, data.propertyName); // -> 1, '1BR Japanese-style Private Room near Kyoto Station'
})
.catch(err => {
console.log(err.message); // -> 'Failed to fetch data.' if it's only 'err', it returns the object.
})
}
function getData() {
return fetchData()
.then(result => {
if(result.success){
return Promise.resolve(result.propertyData);
} else {
return Promise.reject(result.message);
}
})
}
function fetchData() {
return new Promise((resolve, reject) => {
let rand = Math.floor(Math.random()*11)
setTimeout(() => {
if(rand >= 3 && rand <= 10){
resolve({ success: true, propertyData: propertyData });
} else {
reject({ success: false, message: 'Failed to fetch data.' });
}
}, 1000)
})
}
First, I expected err in the error handling in the function handleClick would be the message saying 'Failed to fetch data' because Promise.reject() in the function getData() returns reject.message.
However, it seems to return the object { success: false, message: 'Failed to fetch data.' }.
That is why it needs to set err.message in the function handleClick in order to get the message string.
Does this prove that Promise.reject() always returns object even if you set the return value like result.message?
If so, Promise.resolve() looks behave differently.
It returns result.propertyData, so it doesn't need to set as data.propertyData[key] in the function handleClick unlike the error handling.
Do they, Promise.resolve() and Promise.reject() returns its value differently? or are there something that I missed?
I hope this explains well to describe my confusion.
Thank you.
const propertyData = {
id: 1,
propertyName: "1BR Japanese-style Private Room near Kyoto Station",
};
function handleClick(e) {
fetchData()
.then((data) => {
// We are resolving Object with properties
// 1. success
// 2. propertyData (declared on line 1)
// e.g. Accessing those
// - data.success OR - data.propertyData
const { id, propertyName } = data.propertyData; // Simple destruction of data.propertyData;
console.log(id, propertyName);
})
.catch((err) => {
console.log(err.message); // -> 'Failed to fetch data.' if it's only 'err', it returns the object.
});
}
function fetchData() {
return new Promise((resolve, reject) => {
let rand = Math.floor(Math.random() * 11);
setTimeout(() => {
if (rand >= 3 && rand <= 10) {
//{ This is what will be the resolved data in .then(data => ) }
// It can be anything you want string, Object, boolean w/e
resolve({ success: true, propertyData });
} else {
//{ This is what will be the resolved value in .catch()
// Does not necessarily have to be Object
// But if you pass only string then in your .catch(err => ) : err here will be string
// In your case err will be Object with props 'success' and 'message'
// To access them u go err.message OR err.success
reject({ success: false, message: "Failed to fetch data." });
}
}, 1000);
});
}

axios doesn't return response and error separately

I have a React component. Inside that component I have a function onFormSubmit that calls function from another component. This other function is making POST request with axios.
I would like to return if POST request is true a response into first function or error if not. What is happening now is that my 'SUCCESS RESPONSE' console.log is always triggered, even then there is an error in axios POST request. If there is an error then just 'ERROR RESPONSE' console.log should be triggered.
From first component
onFormSubmit = () => {
postJobDescriptionQuickApply(this.state, this.props.jobDescription.id)
.then((response) => {
console.log('SUCCESS RESPONSE', response)
})
.catch((error) => {
console.log('ERROR RESPONSE', error)
})
}
From second component
export const postJobDescriptionQuickApply = (easyApplyData, jobId) => apiUrl('easyApply', 'easyApply').then(url => axios
.post(url, {
applicant: {
email: easyApplyData.email,
fullName: `${easyApplyData.firstName} ${easyApplyData.lastName}`,
phoneNumber: easyApplyData.phoneNumber,
resume: easyApplyData.resume,
source: easyApplyData.source,
},
job: {
jobId,
},
})
.then((response) => {
console.log('SUCCESS', response.data.developerMessage)
return response.data.developerMessage
})
.catch((error) => {
// handle error
console.log('ERROR JOB DESCRIPTION', error.response.data.developerMessage)
return error.response.data.developerMessage
})
calling return indicates success, and the .catch function in the calling method wouldn't be triggered. Instead of returning error.response.data.developerMessage use throw instead. This will cause it to be thrown and then caught with the .catch method in the calling function.
Depending on the situation though, it's generally not advisable to catch and rethrow exceptions like that because you lose stack trace etc. You may be better off not catching the error in the lower method and just relying on the calling method to handle the error.
In the
.catch((error) => {
// handle error
console.log('ERROR JOB DESCRIPTION', error.response.data.developerMessage)
return error.response.data.developerMessage
})
replace return statement with throw error
Not use catch and catch on your second component.
To can use then and catch on your first component you need return axios, something as:
export const postJobDescriptionQuickApply = (easyApplyData, jobId, url) => axios
.post(url, {
applicant: {
email: easyApplyData.email,
...
},
job: {
jobId,
},
});
// or using apiUrl
export const postJobDescriptionQuickApply = (easyApplyData, jobId) => apiUrl('easyApply', 'easyApply')
.then(url => axios.post(url, {
applicant: {
email: easyApplyData.email,
fullName: `${easyApplyData.firstName} ${easyApplyData.lastName}`,
phoneNumber: easyApplyData.phoneNumber,
resume: easyApplyData.resume,
source: easyApplyData.source,
},
job: {
jobId,
},
});
Additionally, do not forget to validate the response status in the first component, something as:
onFormSubmit = () => {
postJobDescriptionQuickApply(this.state, this.props.jobDescription.id)
.then((response) => {
if (response.status === 200) {
console.log('SUCCESS RESPONSE', response);
}
})
.catch((error) => {
console.log('ERROR RESPONSE', error)
})
}
I hope, I could help you

Pass error after catch on promises

Hi I'm new so sorry if my question does not formulate properly.
I want to define a promise from axios js in a global function.
Here I want to handle / catch the 401 status globally and logout the user.
I do not want to handle it in every single query.
Here my source global function to handle a request:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
}
And here a example function I use on a controller:
requestData('/api/persons', {options: this.options, search: search})
.then(data => {
this.data = data
})
.catch(error => {
this.error = error.toString()
})
My Problem is that the promise catch in my controller will not fire when there is an exception. How to realize this?
change return error in your requestData function to throw error
As per the Axios docs
You can intercept requests or responses before they are handled by then or catch.
You're going to want to use the Response Interceptor:
axios.interceptors.response.use(function(response) {
// Do something with response data
return response;
}, function(error) {
// Do something with response error
if (error.status === 401) {
logout()
}
return Promise.reject(error);
});
Replacing return error by throw error is the half work.
When I'm right the throw error in promise catch will not invoke the next promise .catch statement. This will work in the .then statement.
This way it should work:
export function requestData (url, payload = {}) {
return axios.post(url, payload)
.then(response => {
return response.data
})
.catch(error => {
if (error.response.status === 401) {
logout()
} else {
return error
}
})
.then(result => {
if (result instanceof Error) {
throw result
} else {
return result
}
})
}

Accessing tokens and response data

Trying to figure out some basic authentication with Angular 2. I want my component to check for a token and if it exists navigate to the proper location, but if it doesn't display the error message returned by the service. Here is the the service call:
this._loginService.login(loginQuery)
.subscribe(
(token: any) => this._router.navigate( ['User', { username: user }] ),
(data) => { this.errorMessage = data.ErrorMessage; }
)
Here is the code for the service:
return this._http.post('serivcelink,
body,
{headers:headers})
.map((res : any) => {
let data = res.json();
this.token = data.TeamMember.UserName;
localStorage.setItem('token', this.token);
});
I get the token behavior I want except that I don't get access to the response object in the component, and as such can't display the error message.
How do can I check for the token and get access to the response data?
You can use an if statement to check if your token exists, else display an error message:
this._loginService.login(loginQuery)
.subscribe(
(data: any) => {
if (localStorage.getItem('token')) {
this._router.navigate( ['User', { username: user }] )
} else {
this.errorMessage = data.ErrorMessage;
}
}
)
You need to return something within the map callback, i.e. this.token in your case:
return this._http.post('serivcelink,
body,
{headers:headers})
.map((res : any) => {
let data = res.json();
this.token = data.TeamMember.UserName;
localStorage.setItem('token', this.token);
return this.token; // <-------
});
What you return will be receive as parameter into the callback of the subscribe method.

Categories

Resources