Velvet Star Monitor

Standout celebrity highlights with iconic style.

updates

How to use fetch in TypeScript

Writer Mia Lopez

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(`) .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; })
7

3 Answers

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 })
}
10

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(`) .then(res => res.json<Actor>()) .then(res => { let b:Actor = res; });
4

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(`) .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(`) .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
}

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy