Axios: Comment utiliser Axios avec TypeScript lors de l'utilisation d'intercepteurs de réponse (problème AxiosResponse)

Créé le 30 avr. 2018  ·  50Commentaires  ·  Source: axios/axios

Sommaire

Dans un projet que je migre vers TypeScript (TS), j'ai un intercepteur de réponse r => r.data . Comment puis-je informer TS que je n'attends pas un type de AxiosResponse ? J'ai essayé de remplacer en utilisant as Array<... mais cela ne fonctionne pas car AxiosResponse ne peut pas être converti en Array (par exemple, n'a pas .length).

Merci!

Le contexte

  • version Axios : 0.16.2
  • Environnement : Visual Studio Code

Commentaire le plus utile

Je remplace AxiosResponse dans mon axios.d.ts :

import axios from 'axios'

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

Tous les 50 commentaires

Utilisez la définition de style axios.request<T>(...args) .
Le dernier intercepteur de réponse du tableau se conforme implicitement à une interface telle que (currentResponse: any) => T

Donc, si vous avez data étant quelque chose comme :

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Ensuite, vous pouvez dire :

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 fait, cela ne fonctionnerait pas pour les intercepteurs mondiaux, par exemple axios.interceptors.response.use

Je pense que oui - les intercepteurs sont actuellement très "détendus" (aka any ), donc vous pouvez simplement attacher l'intercepteur de réponse et l'avoir (r: any): any => r.data (ce qui est essentiellement comme si vous le feriez omettez toute saisie en mode non strict).

Ce n'est qu'au moment où vous appelez axios.request que vous pouvez configurer son générique, qui est ensuite transmis comme type de la propriété data .

(En fait, j'ai fait une erreur dans l'extrait de code ci-dessus, data est en fait AxiosResponse<ServerData> qui a un champ appelé .data avec le type ServerData - mis à jour)

@zcei salut désolé mais cela ne fonctionne vraiment pas avec mon exemple, j'ai créé un codesandbox pour mettre en évidence ce problème.

Vérifiez le ici

Ayant le même problème, l'intercepteur peut essentiellement se résumer à :

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

Malgré cela, le type de retour de

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

est AxiosPromise<string> , qui s'attend à ce que les données soient enveloppées.

C'est intentionnel. Vous êtes censé transformer les données dans l'intercepteur, mais pas hisser les clés de réponse .

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

Je pensais que vous auriez une réponse du serveur comme

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

et voulait se débarrasser de l'imbrication response.data.data . Si vous renvoyez response.data dans l'intercepteur, vous pourrez ensuite y accéder via response.data.foo au lieu de response.data.data.foo .

Mais accéder response.foo ne fonctionnerait pas, car il s'agit du niveau de réponse "racine" qui garde une trace d'autres choses, comme le code de réponse et autres.

Désolé, mais je ne suis pas d'accord avec votre prémisse, du moins pas en termes de fonctionnalité.
Sans utiliser l'intercepteur pour déballer la propriété data, je devrais faire

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

dans _chaque_ point de terminaison que je définis. Quel est l'intérêt de toute cette répétition de code ?

Chaque fois qu'une requête réussit, ce qui signifie qu'avoir un then , je ne me soucie pas vraiment du code de réponse, etc. , à la fois sur l'intercepteur et sur des terminaux spécifiques.

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

Chaque fois qu'une requête réussit, ce qui signifie alors, je ne me soucie pas vraiment du code de réponse

Eh bien, vous ne vous souciez peut-être pas d'autres informations, mais contraindre à tout le monde utilisant un client HTTP que vous ne vous souciez que du corps n'est pas vraiment une solution.
Il existe suffisamment de cas d'utilisation légitimes pour les codes d'état et les en-têtes, même lors d'appels réussis. Comme faire des distinctions 204 et 200 , vérifier les en-têtes de limite de débit, extraire les en-têtes Link pour des ressources supplémentaires (pagination), etc. pp.

Si vous vous en fichez, emballez Axios et jetez tout :

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 => { ... });

Je ne vois pas d'où vous vient l'idée que tout le monde devrait faire les choses comme je les fais dans un projet. J'essaie simplement de résoudre un problème de déballage automatique des données à chaque demande et étant donné que ce problème était ici avant moi, je ne suis pas le seul à avoir ce problème.

Les intercepteurs _semblent_ aimer le bon endroit pour le faire, à la fois par leur nom et leur description dans le fichier readme (faire quelque chose avec chaque réponse). Gérer 204 vs 200 etc., puis transmettre les données aurait également du sens dans un intercepteur, car vous pourrez alors le faire à un endroit central.

À mon avis, la solution intuitive serait de renvoyer ce que vous voulez de l'intercepteur, de ne pas forcer la bibliothèque dans un sens.

Si vous souhaitez transformer certains champs en données :

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

Si vous souhaitez simplement déballer les données d'Axios :

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

Cela laisserait le choix de ce qu'il faut faire au développeur, ce qui, à mon avis, est meilleur qu'un intercepteur très opiniâtre. Vous êtes libre de ne pas être d'accord, bien sûr.

Vous raisonniez sur "le but de toute cette répétition de code", alors j'ai juste essayé d'expliquer pourquoi l'information est nécessaire.

Je ne peux pas vraiment trouver un moyen de conserver la sécurité de type dans les intercepteurs et la réponse, si un intercepteur pouvait brouiller l'ensemble de la réponse sans structure commune. :/
Le type de réponse pour une requête devrait devenir any je suppose et mettre l'effort entre les mains des développeurs pour s'assurer qu'ils font la bonne chose.

Cela me semble au moins plus sujet aux erreurs en raison des frappes détendues, que d'accéder à une propriété.

Si vous pouvez proposer un PR qui permet aux intercepteurs d'écraser la réponse racine tout en conservant la sécurité de type pour "les cas d'utilisation à 80 %", je serais plus qu'heureux !

Le fait est que les deux exemples ci-dessus que j'ai faits fonctionnent déjà comme je l'ai décrit, en termes de fonctionnalité, ce sont juste les types qui sont faux. Si renvoyer autre chose que la réponse (modifiée) dans l'intercepteur est erroné, je pense qu'il serait bon de mettre à jour les types attendus ici.

D'abord c'est intéressant de savoir que tu fais la même chose @Etheryte !

Dans notre application, le r => r.data est l'intercepteur de réponse final dans la chaîne et nous en utilisons d'autres qui s'appuient sur des codes d'état pour gérer les jetons d'actualisation, etc., mais à un niveau global car nous n'avons pas besoin de gérer cela pour des raisons spécifiques. appels.

Je comprends qu'il ne soit peut-être pas possible d'adapter ce cas d'utilisation avec TS. Cependant, @zcei , il est indéniable qu'il s'agit d'un moyen légitime d'utiliser Axios car il utilise une partie officielle de l'API Axios (intercepteurs) :).

Oh ! J'ai regardé le mauvais code tout le temps 🤦‍♂️ et j'étais dans la section transformResponse , pas l'intercepteur - je suis vraiment désolé !

Comment mettriez-vous à jour les typages pour tenir compte de l'intercepteur modifiant le type de retour ?

Peut-être quelque chose comme ça ?

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

Vous l'appelleriez ainsi :

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

Je viens d'essayer cela localement et semble faire ce que vous cherchez.
Peut-être que je peux obtenir un PR ensemble dans la soirée pour mettre à jour cela pour toutes les méthodes.

@zcei Ça a l'air bien ! Par défaut, AxiosResponse a bien sûr du sens 99% du temps 👍

Ravi de l'entendre! Désolé encore pour ma confusion 😓

@zcei pas de problème ! Par intérêt, quel est le cycle de publication d'Axios ?

Il n'y en a pas - j'ai encore des choses en attente pour la v1 alpha (#1333) et en attendant @nickuraltsev / @emilyemorehouse font des releases chaque fois que nécessaire.
Depuis v0.18 , il y a eu une certaine traction (y compris mon préféré : les options de portée aux instances), donc je suppose qu'ils pourraient couper une version. Pour plus, je vous inviterais simplement à Gitter , afin que nous gardions les problèmes au point 🙂

J'espérais sortir une version 0.19 mais la dernière fois que j'ai vérifié, master échouait CI. J'aimerais vraiment un calendrier de publication plus solide/régulier une fois que nous aurons la version 1.0.

rencontre le même problème. Existe-t-il des solutions ?

@qidaneix , vous pouvez essayer d'installer npm install axios/axios#master jusqu'à ce qu'il y ait une version. #1605 aurait dû le réparer. Ce serait bien d'avoir des commentaires, si cela aide vraiment avec des cas d'utilisation réels et pas seulement des tests 🙂

@zcei je vais l'essayer demain

@zcei Puis-je demander quand il sortira ?

+1 quand la 1.0.0 sortira ?

Ce sera une version pré-1.0 à coup sûr. @Khaledgarbaya avez-vous également été ajouté à NPM par Matt ? Sinon, nous devons être gentils avec les responsables restants qui le publient 😉

Salut à tous. Je gère actuellement les versions de NPM. J'aimerais publier une autre version pré-1.0.0, mais les tests de Travis échouent et je n'ai pas encore eu l'occasion de les corriger. Une fois qu'ils sont corrigés, je suis plus qu'heureux de le sortir immédiatement 😬

@zcei Je n'ai pas été ajouté au référentiel npm, je ne peux que fusionner les modifications

Y a-t-il une mise à jour sur ce problème, y aura-t-il une version dans un avenir proche, comme le deuxième mois ?

+1

❤️ si cela pouvait sortir maintenant

Nous sommes conscients, mais bloqués actuellement. 🙁 Vous pouvez obtenir plus d'informations ici : https://github.com/axios/axios/issues/1657#issuecomment -410766031

Publié dans le cadre de 0.19.0-beta.1.

Cela peut être installé en utilisant npm install [email protected] ou npm install axios@next

Peut-être quelque chose comme ça ?

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

Vous l'appelleriez ainsi :

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

Je viens d'essayer cela localement et semble faire ce que vous cherchez.
Peut-être que je peux obtenir un PR ensemble dans la soirée pour mettre à jour cela pour toutes les méthodes.

User(T, le premier paramètre générique) ne semble pas utilisé, si je veux utiliser des types de retour personnalisés, j'ai l'air étrange 😕

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

utiliser void peut être plus clair.

@emilyemorehouse Sans vouloir paraître ingrat, mais la version 0.19-beta est ouverte depuis trois mois maintenant, y a-t-il une ETA pour une version GA ? Notre projet a un problème qui nécessite l'un de ces correctifs. J'ai posé cette question sur le canal Gitter mais il ne semble pas que les responsables répondent là-bas ...

Peut-être qu'une meilleure formulation serait quels sont les problèmes qui doivent être résolus avant une version et y a-t-il un endroit pour le suivre ? Puisqu'il semble y avoir beaucoup d'intérêt, étaler le travail et avoir une compréhension claire du travail requis pourrait aider à accélérer les choses.

Il y a déjà un jalon de projet pour la 0.19 mais les tickets listés n'ont pas vu de changement depuis plusieurs mois.

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

Je remplace AxiosResponse dans mon axios.d.ts :

import axios from 'axios'

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

Cogner ce problème. Cela fonctionne ... d'accord quand je viens de copier la définition d'AxiosInstance dans les typages locaux, mais la solution implémentée est très détaillée à mon avis, à moins que je ne fasse quelque chose de mal (pas un expert Typescript). Depuis que j'utilise une instance axios distincte créée avec axios.create et en utilisant cet intercepteur :

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

response.data a toujours cette forme :

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

il semble que je doive utiliser AxiosClient.post comme ceci :

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

pour avoir les types appropriés dans .then . Est-ce que je fais quelque chose de mal ici ou est-ce que ça devrait vraiment être QUE verbeux ? Sinon, ce serait bien mieux si je pouvais simplement passer le schéma de réponse attendu lors de la création de l'instance, mais je ne peux pas le faire fonctionner:

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

Cela fonctionne bien avec axios.create() sans type générique ou juste axios , mais si je passe mon interface comme ceci :

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

puis utilisez-le comme ceci: AxiosClient.post<string>('/endpoint').then(value => value.data) , value.data a le type ... T. De plus, la version ci-dessus ne fonctionne que si je remplace réellement ces typages dans node_modules, sinon cela devient totalement mélangé et je se retrouver avec un désastre total. Quelqu'un pourrait-il m'aider avec ça?

Edit: d'accord, je suppose que cela ne fonctionnera pas car il n'est pas possible d'utiliser des génériques de cette manière (donc R<T> lorsque R est un type générique n'est pas la syntaxe correcte mais je suppose que WebStorm pour une raison quelconque n'a pas mis en évidence ça pour moi); https://github.com/Microsoft/TypeScript/issues/1213 cela, je suppose, le résoudrait, mais aucune idée s'il est jamais mis en œuvre.

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

utiliser:

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

@zcei Est-ce déjà résolu? Je n'arrive pas à faire fonctionner ce qui suit :

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

ayant le même problème, j'installe la version 0.19beta, également ts ne peut pas analyser le type correct

Je remplace AxiosResponse dans mon axios.d.ts :

import axios from 'axios'

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

oh niupi

J'utilise interceptor pour faire quelque chose comme: response => response.data . Je dois donc supprimer complètement le wrapper AxiosResponse .

Je finis finalement par :

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

Si quelqu'un ne sait pas s'en servir :

  1. Écrivez les types quelque part, par exemple src/types/axios/axios.d.ts .
  2. Mettez à jour votre tsconfig.json , par exemple
{
  "compilerOptions": {
    "typeRoots": [
        "./node_modules/@types",
        "./src/types/",
    ]
  }
}

Cela fonctionne pour moi:

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

une autre façon de passer outre

import * as axios from 'axios'

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

Je pense que nous ne devrions peut-être pas ajouter de propriété à AxiosResponse lorsque nous utilisons des intercepteurs , car les intercepteurs peuvent être éjectés.

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

pourquoi utilisons-nous le tapuscrit? car nous espérons que notre projet se déroulera en toute sécurité.
si un jour nous supprimons une propriété des utilitaires de base, nous aimerions que le code qui y fait référence produise une erreur au moment de la compilation.

donc, nous voulons utiliser l' intercepteur ajouter une propriété à AxiosResponse et avoir une inférence de type, c'est contradictoire, car il n'y a aucun moyen de garantir que l'inférence de type peut être mise à jour lorsque l'intercepteur est éjecté

Je pense qu'axios devrait dire à l'utilisateur :
l'intercepteur doit gérer quelque chose qui n'a aucun effet sur AxiosResponse , si vous voulez étendre la propriété AxiosResponse et avoir une inférence de type, vous devriez aimer 'plugin'

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

ce n'est pas une sécurité à 100 %, mais c'est une sécurité que d'utiliser simplement axios.interceptors.response.use

et je recommande axios desgin un système de plugin, maintenant on voit toujours comme

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

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

utilisez comme wrapper pour connecter un plugin à axios, chaque plugin n'a pas de règle commune, ce n'est pas assez élégant. je pense que ça devrait plaire

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

Utilisez la définition de style axios.request<T>(...args) .
Le dernier intercepteur de réponse du tableau se conforme implicitement à une interface telle que (currentResponse: any) => T

Donc, si vous avez data étant quelque chose comme :

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

Ensuite, vous pouvez dire :

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

Pls pouvez-vous me dire comment je peux ajouter un en-tête et une configuration dans la demande Axios avec tsx

@MoZiadAlhafez

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

Mais ce n'est pas recommandé

C'est une longue histoire, mais semble avoir été résolue dans #1605. Voir https://github.com/axios/axios/issues/1510#issuecomment -396894600.

Si j'ai mal compris quelque chose, mieux vaut ouvrir un nouveau sujet avec des références ici. Merci.

Cette page vous a été utile?
0 / 5 - 0 notes