How to use fetch in typescript

后端 未结 3 1121
慢半拍i
慢半拍i 2020-11-29 20:07

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

相关标签:
3条回答
  • 2020-11-29 20:51

    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
        })
    }
    
    0 讨论(0)
  • 2020-11-29 20:56

    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;
          });
    
    0 讨论(0)
  • 2020-11-29 20:56

    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
    }
    
    0 讨论(0)
提交回复
热议问题