如何在 TypeScript 中使用 fetch

Posted

技术标签:

【中文标题】如何在 TypeScript 中使用 fetch【英文标题】:How to use fetch in TypeScript 【发布时间】:2017-04-27 11:12:24 【问题描述】:

我在 Typescript 中使用 window.fetch,但我无法将响应直接转换为我的自定义类型:

我通过将 Promise 结果转换为中间“任何”变量来解决这个问题。

这样做的正确方法是什么?

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;
      )

【问题讨论】:

呃,json 包含普通对象,那么如何将其转换为实例呢?您需要使用 Actor.from 之类的东西来创建带有数据的 new Actor 为什么“不允许”?尝试时遇到什么错误? 你使用了哪些定义,因为fetch isn't in typescript libs yet 啊,对不起,我刚刚发现了错误:我不得不说 res 的类型是 any。 .then((res:any) => let b = res)。然后它实际上是允许的。 @MeirionHughes 我正在使用肯定类型的 whatwg-fetch.d.ts 文件来使打字稿识别 fetch。 @Timo 这个评论应该是针对 Meirion 的吗? 【参考方案1】:

下面是几个示例,从基本到在请求和/或错误处理之后添加转换:

基本:

// 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 */
  )

数据转换:

通常您可能需要在将数据传递给消费者之前对其进行一些调整,例如,解开***数据属性。这是直截了当的:

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 */
  )

错误处理:

我认为您不应该直接在此服务中直接捕获错误,而只是让它冒泡,但如果需要,您可以执行以下操作:

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 */
  )

编辑

自从不久前写下这个答案以来,已经发生了一些变化。如 cmets 中所述,response.json&lt;T&gt; 不再有效。不确定,找不到它被删除的位置。

对于以后的版本,您可以:

// 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
    )

【讨论】:

谢谢,这是迄今为止我读过的对泛型的最佳解释。为什么 Promise 可以是一种类型仍然有点模糊,而实际上它是具有该类型的数据...... 太棒了!我最近一直在探索 TS 的这一部分,所以它有助于我记下我的笔记。哪个部分令人困惑? - 很高兴扩展它 我希望不是 Promise 具有 &lt;T&gt; 类型,而是正在获取的内容。但显然你可以告诉 Promise 类? (Promise 是一个类吗?一个函数?一个对象?) response.json 方法似乎没有被定义为通用方法——在当前的@types/node-fetch 和当前的 TypeScript lib.dom.d.ts 中都没有——所以这个答案现在不可行。所以我想我们必须这样做return response.json() as Promise&lt;T&gt;;? @ChrisW 你是对的,它已经改变了。我已经更新了答案【参考方案2】:

如果你看一下@types/node-fetch,你会看到正文定义

export class Body 
    bodyUsed: boolean;
    body: NodeJS.ReadableStream;
    json(): Promise<any>;
    json<T>(): Promise<T>;
    text(): Promise<string>;
    buffer(): Promise<Buffer>;

这意味着您可以使用泛型来实现您想要的。我没有测试这段代码,但它看起来像这样:

import  Actor  from './models/actor';

fetch(`http://swapi.co/api/people/1/`)
      .then(res => res.json<Actor>())
      .then(res => 
          let b:Actor = res;
      );

【讨论】:

添加泛型类型会导致Expected 0 type arguments, but got 1,但也许那是因为我不使用node-fetch。原生 fetch 的类型可能不同? 链接文件没有模板化的json()。如果它以前存在,它就不再存在了。 很抱歉挖掘了一个旧帖子,但是,Body.json&lt;T&gt; 签名已在 2018 年与 this commit 从 distinctlyTyped 中删除。浏览node-fetch > /src/body.js 的历史,我不确定我是否见过以这种方式实现json() 的时间。 Nico,你有这个工作的例子吗,否则,我倾向于认为这没有。 嗯,我是从 2017 年的一些代码中得到的......我现在不知道这个状态【参考方案3】:

实际上,几乎在打字稿中的任何地方,只要传递的类型兼容,将值传递给具有指定类型的函数就可以正常工作。

话虽这么说,以下工作......

 fetch(`http://swapi.co/api/people/1/`)
      .then(res => res.json())
      .then((res: Actor) => 
          // res is now an Actor
      );

我想将我所有的 http 调用包装在一个可重用的类中——这意味着我需要某种方法让客户端以所需的形式处理响应。为了支持这一点,我接受一个回调 lambda 作为我的包装方法的参数。 lambda 声明接受任何类型,如此处所示...

callBack: (response: any) => void

但在使用中,调用者可以传递一个指定所需返回类型的 lambda。我像这样从上面修改了我的代码...

fetch(`http://swapi.co/api/people/1/`)
  .then(res => res.json())
  .then(res => 
      if (callback) 
        callback(res);    // Client receives the response as desired type.  
      
  );

这样客户端就可以通过回调调用它...

(response: IApigeeResponse) => 
    // Process response as an IApigeeResponse

【讨论】:

以上是关于如何在 TypeScript 中使用 fetch的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中使用 RequestPromise?

如何在 Typescript 2.1+ 中使用 Bluebird

如何在Vue项目中使用Typescript

如何在 Typescript 中使用 Sinon?

如何在 Typescript 中使用 Webpack 'require' 和 'require.ensure'

如何在 TypeScript 中使用 three.js 加载 OBJ 模型