Axios: Cómo usar Axios con TypeScript al usar interceptores de respuesta (problema de AxiosResponse)

Creado en 30 abr. 2018  ·  50Comentarios  ·  Fuente: axios/axios

Resumen

En un proyecto que estoy migrando a TypeScript (TS), tengo un interceptor de respuesta r => r.data . ¿Cómo le informo a TS que no espero un tipo de AxiosResponse ? Intenté anular usando as Array<... pero eso no funciona ya que AxiosResponse no se puede convertir como Array (por ejemplo, no tiene .longitud).

¡Gracias!

Contexto

  • versión de axios: 0.16.2
  • Entorno: código de Visual Studio

Comentario más útil

Anulo AxiosResponse en mi axios.d.ts :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

Todos 50 comentarios

Utilice la definición de estilo axios.request<T>(...args) .
El último interceptor de respuesta en la matriz se ajusta implícitamente a una interfaz como (currentResponse: any) => T

Entonces, si tiene data siendo algo como:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Entonces puedes decir:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

@zcei ooh, en realidad esto no funcionaría para interceptores globales, por ejemplo axios.interceptors.response.use

Creo que sí: los interceptores actualmente tienen un tipo muy "relajado" (también conocido como any ), por lo que puede adjuntar el interceptor de respuesta y tenerlo (r: any): any => r.data (que es básicamente como si omita cualquier escritura en modo no estricto).

Solo en el punto en el que llama a axios.request puede configurarlo como genérico, que luego pasa como el tipo de la propiedad data .

(En realidad cometí un error en el fragmento de código anterior, data es en realidad AxiosResponse<ServerData> que tiene un campo llamado .data con el tipo ServerData - actualizado)

@zcei hola, lo siento, pero esto realmente no funciona con mi ejemplo, he creado un codesandbox para resaltar este problema.

Compruébalo aquí

Teniendo el mismo problema, el interceptor se puede reducir esencialmente a:

this.instance.interceptors.response.use(response => response.data);

A pesar de esto, el tipo de retorno de

this.instance.post<string>(...);

es AxiosPromise<string> , que espera que se ajusten los datos.

Esto es intencional. Se supone que debe transformar los datos en el interceptor, pero no izar las claves de respuesta .

https://github.com/axios/axios/blob/0b3db5d87a60a1ad8b0dce9669dbc10483ec33da/lib/core/dispatchRequest.js#L63 -L67

Pensé que tendrías una respuesta del servidor como

{
  "data": {
    "foo": 42
  }
}

y quería deshacerse de la anidación de response.data.data . Si devuelve response.data en el interceptor, podrá acceder más tarde a través response.data.foo en lugar de response.data.data.foo .

Pero acceder a response.foo no funcionaría, ya que este es el nivel de respuesta "raíz" que realiza un seguimiento de otras cosas, como el código de respuesta y similares.

Lo siento, pero no estoy de acuerdo con tu premisa, al menos no en cuanto a la funcionalidad.
Sin usar el interceptor para desenvolver la propiedad de datos, tendría que hacer

this.instance.post<string>(...).then(response => response.data);
this.instance.get<number>(...).then(response => response.data);

en _cada_ punto final que defino. ¿Cuál es el punto en toda esa repetición de código?

Cada vez que una solicitud tiene éxito, lo que significa que tiene then , realmente no me importa el código de respuesta, etc. Solo me importan los códigos de respuesta si algo _no_ funcionó, y para eso están los controladores de errores , tanto en el interceptor como en puntos finales específicos.

this.instance.interceptors.response.use(response => {
  // Everything went well, pass only relevant data through
  return response.data;
}, error => {
  // Something went wrong, figure out how to handle it here or in a `.catch` somewhere down the pipe
});

Cada vez que una solicitud tiene éxito, lo que significa que tiene un entonces, realmente no me importa el código de respuesta

Bueno, es posible que no le importe ninguna otra información, pero restringir a todos los que usan un cliente HTTP de que solo se preocupan por el cuerpo no es realmente una solución.
Hay suficientes casos de uso legítimos para códigos de estado y encabezados incluso en llamadas exitosas. Como hacer distinciones en 204 vs 200 , verificar encabezados de límite de tasa, extraer encabezados de Link para recursos adicionales (paginación), etc. págs.

Si no te importa, envuelve Axios y tira todo:

this.instance.request = <T>(...args) => {
  return axios.request<T>(...args).then(({ data }) => data)
}

this.instance.request<string>({ method: 'post', ... }).then(data => { ... });
this.instance.request<number>({ method: 'get', ... }).then(data => { ... });

No veo de dónde sacaste la idea de que todo el mundo debería hacer las cosas como yo las hago en un proyecto. Simplemente estoy tratando de resolver un problema de cómo desenvolver automáticamente los datos en cada solicitud y dado que este problema estaba aquí antes que yo, no soy el único con ese problema.

Los interceptores _parecen_ como el lugar correcto para hacerlo, tanto por su nombre como por su descripción en el archivo Léame (hacer algo con cada respuesta). Manejar 204 frente a 200 etc. y luego pasar los datos también tendría sentido en un interceptor porque entonces puedes hacerlo en un lugar central.

En mi opinión, la solución intuitiva sería devolver lo que quieras del interceptor, no hacer que la biblioteca fuerce de una sola manera.

Si desea transformar algunos campos en datos:

this.instance.interceptors.response.use(response => {
  response.data.foo = JSON.parse(response.data.bar);
  return response;
});

Si solo desea desenvolver los datos de Axios:

this.instance.interceptors.response.use(response => response.data);

Esto dejaría la elección de qué hacer al desarrollador, lo que en mi opinión es mejor que un interceptor muy testarudo. Eres libre de estar en desacuerdo, por supuesto.

Estabas razonando sobre "el punto en toda esa repetición de código", así que solo traté de explicar por qué la información es necesaria.

Realmente no puedo encontrar una manera de cómo le gustaría mantener la seguridad de tipos en los interceptores y la respuesta, si algún interceptor pudiera codificar toda la respuesta sin una estructura común. :/
El tipo de respuesta para una solicitud debería convertirse en any , supongo, y poner el esfuerzo en manos de los desarrolladores para asegurarse de que hagan lo correcto.

Eso, al menos para mí, suena más propenso a errores debido a la escritura relajada, que acceder a una propiedad.

Si puede encontrar un PR que permita a los interceptores sobrescribir la respuesta raíz mientras mantiene la seguridad de tipo para "los casos de uso del 80%", ¡estaría más que feliz!

La cuestión es que los dos ejemplos anteriores que hice ya funcionan de la manera que describí, en cuanto a la funcionalidad, son solo los tipos los que están mal. Si devolver algo más que la respuesta (modificada) en el interceptor es incorrecto, creo que sería bueno actualizar los tipos esperados allí.

¡Primero, es interesante saber que estás haciendo lo mismo @Etheryte !

En nuestra aplicación r => r.data es el interceptor de respuesta final en la cadena y usamos otros que se basan en códigos de estado para manejar tokens de actualización, etc. llamadas

Entiendo que podría no ser posible acomodar este caso de uso con TS. Sin embargo, @zcei es innegable que esta es una forma legítima de usar Axios, ya que usa una parte oficial de la API de Axios (interceptores) :).

¡Do! Estuve mirando el código incorrecto todo el tiempo 🤦‍♂️ y estaba en la sección transformResponse , no en el interceptor. ¡Lo siento mucho!

¿Cómo actualizaría las tipificaciones para tener en cuenta que el interceptor cambia el tipo de devolución?

¿Quizás algo como esto?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

Lo llamarías así:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

Acabo de probarlo localmente y parece hacer lo que estás buscando.
Tal vez pueda reunirme con un RP por la noche para actualizar eso para todos los métodos.

@zcei ¡ Eso se ve bien! La configuración predeterminada de AxiosResponse, por supuesto, tiene sentido el 99 % de las veces 👍

¡Me alegro de oirlo! Perdón de nuevo por mi confusión 😓

@zcei no hay problema! Por interés, ¿cuál es el ciclo de lanzamiento de Axios?

No hay ninguno. Todavía tengo algunas cosas pendientes para la versión alfa v1 (# 1333) y, mientras tanto, @nickuraltsev / @emilyemorehouse están haciendo lanzamientos cada vez que es necesario.
Desde v0.18 ha habido algo de tracción (incluido mi favorito: opciones de alcance para instancias), así que supongo que podrían lanzar un lanzamiento. Para obtener más información, amablemente lo invito a Gitter , para que podamos mantener los problemas en el punto 🙂

Tenía la esperanza de obtener una versión 0.19, pero la última vez que lo comprobé, el maestro estaba fallando en CI. Definitivamente me gustaría un calendario de lanzamiento más sólido/regular una vez que lleguemos a la versión 1.0.

cumplir con el mismo problema. ¿Hay alguna solución?

@qidaneix podría intentar instalar npm install axios/axios#master hasta que haya un lanzamiento. # 1605 debería haberlo arreglado. Sería bueno recibir algunos comentarios, si eso realmente ayuda con los casos de uso de la vida real y no solo con las pruebas 🙂

@zcei lo probaré mañana

@zcei ¿Puedo preguntar cuándo se lanzará?

+1 cuando se lanzará 1.0.0?

Seguro que será una versión anterior a la 1.0. @Khaledgarbaya , ¿Matt también te agregó a NPM? De lo contrario, debemos ser amables con los mantenedores restantes que lo lanzan 😉

Oigan todos. Actualmente estoy gestionando los lanzamientos de NPM. Me encantaría publicar otra versión anterior a la 1.0.0, pero las pruebas de Travis están fallando y aún no he tenido la oportunidad de solucionarlas. Una vez que estén arreglados, estoy más que feliz de sacar esto de inmediato 😬

@zcei No me agregaron al repositorio de npm, solo puedo fusionar cambios

¿Hay alguna actualización sobre este problema, habrá un lanzamiento en un futuro cercano, como el mes dos?

+1

❤️ si esto pudiera ser lanzado ahora

Somos conscientes, pero bloqueados actualmente. 🙁 Puede obtener más información aquí: https://github.com/axios/axios/issues/1657#issuecomment -410766031

Publicado como parte de 0.19.0-beta.1.

Esto se puede instalar usando npm install [email protected] o npm install axios@next

¿Quizás algo como esto?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

Lo llamarías así:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

Acabo de probarlo localmente y parece hacer lo que estás buscando.
Tal vez pueda reunirme con un RP por la noche para actualizar eso para todos los métodos.

Parece que el usuario (T, el primer parámetro genérico) no se usa, si quiero usar tipos de devolución personalizados, se ve extraño 😕

axios.request<void, string>({
    ...
})

usar void puede ser más claro.

@emilyemorehouse No quiero sonar desagradecido, pero la versión beta 0.19 ha estado abierta durante tres meses, ¿hay una ETA para un lanzamiento de GA? Nuestro proyecto tiene un problema que requiere una de estas correcciones. Pregunté esto en el canal Gitter pero no parece que los mantenedores respondan allí...

Tal vez una mejor redacción sería ¿cuáles son los problemas que deben resolverse antes de un lanzamiento y hay un lugar para rastrearlo? Dado que parece haber mucho interés, distribuir el trabajo y tener una comprensión clara del trabajo requerido podría ayudar a acelerar las cosas.

Ya hay un hito de proyecto para 0.19, pero los boletos enumerados no han visto ningún cambio durante varios meses.

const func1: any = () => { return axios.request(...) }

Anulo AxiosResponse en mi axios.d.ts :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

Golpeando este problema. Funciona... está bien cuando acabo de copiar la definición de AxiosInstance a los tipos locales, pero la solución implementada es muy detallada en mi opinión, a menos que esté haciendo algo mal (no soy un experto en TypeScript). Como estoy usando una instancia de axios separada creada con axios.create y usando este interceptor:

AxiosClient.interceptors.response.use(
  response => response && response.data,
  error => error && error.response && error.response.data
);

donde response.data siempre tiene esta forma:

export interface APIResponse<T = any> {
  data: T;
  message: string;
  status: boolean;
}

parece que tengo que usar AxiosClient.post así:

AxiosClient.post<EndpointAPIResponse, APIResponse<EndpointAPIResponse>>('/endpoint', { someData });

tener tipos adecuados en .then . ¿Estoy haciendo algo mal aquí o realmente debería ser TAN detallado? De lo contrario, sería mucho mejor si pudiera pasar el esquema de respuesta esperado al crear la instancia, pero no puedo hacer que funcione:

export interface AxiosInstance<R> {
  <T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  <T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  defaults: AxiosRequestConfig;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<R>;
  };
  getUri(config?: AxiosRequestConfig): string;
  request<T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  head<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
}

export interface AxiosStatic extends AxiosInstance<AxiosResponse> {
  create<T = AxiosResponse>(config?: AxiosRequestConfig): AxiosInstance<T>;
  Cancel: CancelStatic;
  CancelToken: CancelTokenStatic;
  isCancel(value: any): boolean;
  all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
  spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
}

Funciona bien con axios.create() sin un tipo genérico o solo axios , pero si paso mi interfaz así:

const AxiosClient = axios.create<APIResponse>({
  // ...
});

y luego utilícelo así: AxiosClient.post<string>('/endpoint').then(value => value.data) , value.data tiene tipo... T. Además, la versión anterior solo funciona si realmente reemplazo estos tipos en node_modules, de lo contrario, se mezcla totalmente y yo terminar con un desastre total. ¿Alguien podría ayudarme con esto?

Editar: está bien, supongo que no funcionará porque no es posible usar genéricos de esa manera (así que R<T> cuando R es un tipo genérico no es la sintaxis correcta, pero supongo que WebStorm por alguna razón no resaltó es para mi); https://github.com/Microsoft/TypeScript/issues/1213 esto, supongo, lo resolvería, pero no tengo idea si alguna vez se implementa.

const request = <T>(options: AxiosRequestConfig) => {
    return axios.request<IResponse<T>>(options).then<T>(response => {
        const data = response.data;
        if (data.c !== '0') {
            return Promise.reject(new Error(data.m || 'error'));
        }
        return data.d;
    });
}

utilizar:

request<IApiGetBrandGoodsInfoByIds>({
        method: 'GET',
        url: '/api/a/b',
    });

@zcei ¿Ya se resolvió esto? No puedo hacer que lo siguiente funcione:

// so I created an axios instance:
const instance = axios.create(someOptions);

// then used interceptors
instance.interceptors.response.use((res) => res.data.data);   // server response will always have 'data'

// then when using the following to make a request
instance.get<string>(someURI);  // suppose server response was {data: 'some message'}

// ^^ the above returns type AxiosPromise<string>. How do I get it to return type Promise<string> instead?

teniendo el mismo problema, instalé la versión 0.19beta, también ts no puede analizar el tipo correcto

Anulo AxiosResponse en mi axios.d.ts :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

oh niupi

Uso interceptor para hacer algo como: response => response.data . Así que necesito quitar el envoltorio AxiosResponse completo.

Finalmente termino con:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosInstance {
    request<T = any> (config: AxiosRequestConfig): Promise<T>;
    get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  }
}

Por si alguien no sabe cómo usarlo:

  1. Escriba los tipos en algún lugar, por ejemplo, src/types/axios/axios.d.ts .
  2. Actualice su tsconfig.json , por ejemplo
{
  "compilerOptions": {
    "typeRoots": [
        "./node_modules/@types",
        "./src/types/",
    ]
  }
}

Esto funciona para mí:

Api.boards.createBoard = jest.fn((name:string) => {
      const mockBoardResult = {
        created_on: mockBoardData.date,
        name,
        threads: [],
        updated_on: mockBoardData.date,
        _id: mockBoardData._id
      };
      return Promise.resolve({data:mockBoardResult}) as Promise<AxiosResponse<any>>
    });

otra forma de anular

import * as axios from 'axios'

declare module 'axios' {
  interface AxiosInstance {
    (config: AxiosRequestConfig): Promise<any>
  }
}

Creo que tal vez no deberíamos agregar propiedades a AxiosResponse cuando usamos interceptores , porque los interceptores pueden ser expulsados.

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default addFooToResp
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

var id = axios.interceptors.response.use(addFooToResp)

axios.get('xx').then(res => {
  // have type defined
  // because we use `declare module 'axios'` ts can infer type
  console.log(res.foo)
})

// but if then eject?
axios.interceptors.response.eject(id)
axios.get('xx').then(res => {
  // also have type defined, it's maybe not reasonable
  console.log(res.foo)
})

¿Por qué usamos mecanografiado? porque esperamos que nuestro proyecto sea seguro.
si algún día eliminamos una propiedad de las utilidades base, nos gustaría que el código que hace referencia a ella produzca un error en tiempo de compilación.

entonces, queremos usar el interceptor agregar alguna propiedad a AxiosResponse y tener inferencia de tipo, es contradictorio, porque no hay forma de garantizar que la inferencia de tipo se pueda actualizar cuando se expulsa el interceptor

Creo que axios debería decirle al usuario:
el interceptor debe manejar algo que no tenga efecto en AxiosResponse , si desea extender la propiedad AxiosResponse y tener una inferencia de tipo, le gustaría 'complementar'

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default (axios) => {
  axios.interceptors.response.use(addFooToResp)
}
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

addFooToResp(axios)

no es 100% seguro, pero es más seguro que usar axios.interceptors.response.use

y recomiendo axios diseñar un sistema de complementos, ahora siempre vemos como

import axios from 'axios'
import wrapper from 'axios-cache-plugin'

let http = wrapper(axios, {
  maxCacheSize: 15
})
export default http

use como wrapper para conectar un complemento a axios, cada complemento no tiene una regla común, no es lo suficientemente elegante. creo que debería gustar

import axios from 'axios'
import axiosCache from 'axios-cache-plugin'

axios.use(axiosCache, {
  // some options
})
export axios
// axios-cache-plugin
export default (axios) => {}
// or
export default {
  install(axios){}
}
// like Vue.use for Vue.js

Utilice la definición de estilo axios.request<T>(...args) .
El último interceptor de respuesta en la matriz se ajusta implícitamente a una interfaz como (currentResponse: any) => T

Entonces, si tiene data siendo algo como:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Entonces puedes decir:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

Por favor, ¿puede decirme cómo puedo agregar encabezado y configuración en la solicitud de Axios con tsx?

@MoZiadAlhafez

declare module 'axios' {
  interface AxiosRequestConfig {
    useCache: boolean
  }
}

Pero esto no es recomendable

Esta es una larga historia, pero parece haberse resuelto en el #1605. Consulte https://github.com/axios/axios/issues/1510#issuecomment -396894600.

Si entendí algo mal, mejor abrir un nuevo número con referencias aquí. Gracias.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Shu-Ji picture Shu-Ji  ·  3Comentarios

ghost picture ghost  ·  3Comentarios

tbaustin picture tbaustin  ·  3Comentarios

reggi picture reggi  ·  3Comentarios

Adman picture Adman  ·  3Comentarios