Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
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 */
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
–
–
–
–
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
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;
–
–
–
–
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
})();
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.