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; })
In TypeScript, we can use the fetch function to consume typed response data.
The fetch() method in JavaScript is used to request to the server and load the information on the webpages. The request can be of any APIs that return the data of the format JSON or XML. This method returns a promise. Syntax: fetch('url') //api for the get request .
A few examples follow, going from basic through to adding transformations after the request and/or error handling:
// 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 */ })
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 */ })
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 */ })
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; });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With