Thwack est :
Ce README est un travail en cours. Vous pouvez également me poser une question sur Twitter.
$ npm i thwack
ou
$ yarn add thwack
Axios était génial lors de sa sortie à l’époque. Cela nous a donné un wrapper basé sur une promesse autour de XMLHttpRequest
, qui était difficile à utiliser. Mais c’était il y a longtemps et les temps ont changé : les navigateurs sont devenus plus intelligents. Il est peut-être temps que votre solution de récupération de données suive le rythme ?
Thwack a été conçu dès le départ en pensant aux navigateurs modernes. Pour cette raison, il n’a pas le bagage d’Axios. Axios pèse environ ~ 5 000 gzippés. Thwack, en revanche, est un mince ~1,5k.
Ils prennent en charge la même API, mais il existe quelques différences, principalement autour options
, mais pour la plupart, ils devraient pouvoir être utilisés de manière interchangeable pour de nombreuses applications.
Thwack n'essaie pas de résoudre tous les problèmes, comme le fait Axios, mais fournit plutôt la solution à 98 % de ce dont les utilisateurs ont réellement besoin. C'est ce qui confère à Thwack sa légèreté comme une plume.
Grattez ça. Thwack offre le même niveau de puissance qu'Axios avec un encombrement beaucoup plus réduit. Et le système d'événements basé sur les promesses de Thwack est plus facile à utiliser.
Les méthodes suivantes sont disponibles sur toutes les instances Thwack.
thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.request(options: ThwackOptions): Promise<ThwackResponse>
thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;
thwack.create(options: ThwackOptions): ThwackInstance;
La méthode create
crée (da!) une nouvelle instance enfant de l'instance Thwack actuelle avec les options
données.
thwack.getUri(options: ThwackOptions): string;
La résolution d'URL Thwacks est conforme à la RFC-3986. Celui d’Axios ne l’est pas. Il est alimenté par @thwack/resolve
.
Thwack prend en charge les types d'événements suivants : request
, response
, data
et error
.
Pour plus d'informations sur le système d'événements de Thwack, voir Événements Thwack ci-dessous.
thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
Thwack dispose des fonctions d'assistance suivantes pour effectuer des requêtes simultanées. Ils sont principalement destinés à la compatibilité Axios. Voir la section « Comment faire » ci-dessous pour un exemple d'utilisation.
thwack.all(Promise<ThwackResponse>[])
thwack.spread(callback<results>)
L'argument options
a les propriétés suivantes.
url
Il s'agit soit d'une URL complète, soit d'une URL relative.
baseURL
Définit une URL de base qui sera utilisée pour créer une URL complète à partir de url
ci-dessus. Il doit s'agir d'une URL absolue ou undefined
. La valeur par défaut est l' origin
+ pathname
de la page Web actuelle si elle est exécutée dans un navigateur ou si undefined
sur Node ou React Native.
Par exemple, si vous avez fait ceci :
thwack ( 'foo' , {
baseURL : 'http://example.com' ,
} ) ;
l'URL récupérée sera :
http://example.com/foo
method
Chaîne contenant l'une des méthodes HTTP suivantes : get
, post
, put
, patch
, delete
ou head
.
data
Si la method
est post
, put
ou patch
, ce sont les données qui seront utilisées pour créer le corps de la requête.
headers
C'est ici que vous pouvez placer tous les en-têtes de requête HTTP facultatifs. Tout en-tête que vous spécifiez ici est fusionné avec toutes les valeurs d'en-tête d'instance.
Par exemple, si nous définissons une instance Thwack comme ceci :
const api = thwack . create ( {
headers : {
'x-app-name' : 'My Awesome App' ,
} ,
} ) ;
Puis plus tard, lorsque vous utilisez l'instance, vous effectuez un appel comme celui-ci :
const { data } = await api . get ( 'foo' , {
headers : {
'some-other-header' : 'My Awesome App' ,
} ,
} ) ;
Les en-têtes qui seraient envoyés sont :
x-app-name: My Awesome App
some-other-header': 'My Awesome App'
defaults
Cela vous permet de lire/définir les options par défaut pour cette instance et, en fait, pour toutes les instances enfants.
Exemple:
thwack . defaults . baseURL = 'https://example.com/api' ;
Pour une instance, defaults
est le même objet passé à create
. Par exemple, ce qui suit affichera « https://example.com/api ».
const instance = thwack . create ( {
baseURL : 'https://example.com/api' ,
} ) ;
console . log ( instance . defaults . baseURL ) ;
Notez également que la définition defaults
sur une instance (ou même la transmission options
) à une instance n'affecte PAS le parent. Ainsi, pour l'exemple suivant, thwack.defaults.baseURL
sera toujours "https://api1.example.net/".
thwack . defaults . baseURL = 'https://api1.example.net/' ;
const instance = thwack . create ( ) ;
instance . defaults . baseURL = 'https://example.com/api' ;
console . log ( thwack . defaults . baseURL ) ;
params
Il s'agit d'un objet facultatif qui contient les paires clé/valeur qui seront utilisées pour créer l'URL de récupération. S'il y a des segments :key
de la baseURL
ou de l' url
, ils seront remplacés par la valeur de la clé correspondante. Par exemple, si vous avez fait ceci :
thwack ( 'orders/:id' , {
params : { id : 123 } ,
baseURL : 'http://example.com' ,
} ) ;
l'URL récupérée sera :
http://example.com/orders/123
Si vous ne spécifiez pas de :name
, ou s'il y a plus de param
s que de :name
, alors les clés/valeurs restantes seront définies comme paramètres de recherche (c'est-à-dire ?key=value
).
maxDepth
Le niveau maximum de requêtes récursives pouvant être effectuées dans un callbck avant que Thwack ne génère une erreur. Ceci est utilisé pour empêcher un rappel d'événement de provoquer une boucle récursive, ceci s'il émet une autre request
sans que les garanties appropriées soient mises en place. Par défaut = 3.
responseType
Par défaut, Thwack déterminera automatiquement comment décoder les données de réponse en fonction de la valeur de l'en-tête de réponse content-type
. Toutefois, si le serveur répond avec une valeur incorrecte, vous pouvez remplacer l'analyseur en définissant responseType
. Les valeurs valides sont arraybuffer
, document
(c'est-à-dire formdata
), json
, text
, stream
et blob
. La valeur par défaut est automatique.
Ce qui est renvoyé par Thwack est déterminé par le tableau suivant. La colonne « méthode de récupération » correspond à ce qui est résolu dans data
. Si vous ne spécifiez pas de responseType
, Thwack déterminera automatiquement la méthode de récupération en fonction content-type
et de la table responseParserMap
(voir ci-dessous).
Type de contenu | responseType | méthode fetch |
---|---|---|
application/json | json | response.json() |
multipart/form-data | formdata | response.formData() |
text/event-stream | stream | renvoie response.body sous forme data sans traitement |
blob | response.blob() | |
arraybuffer | response.arrayBuffer() | |
*/* | text | response.text() |
Remarque :
stream
n'est actuellement pas pris en charge dans React Native en raison du #27741
responseParserMap
Un autre moyen utile de déterminer quel analyseur de réponse utiliser consiste à utiliser responseParserMap
. Il vous permet de configurer un mappage entre le content-type
résultant de l'en-tête de réponse et le type de l'analyseur.
Thwack utilise la carte suivante par défaut, qui permet le décodage json
et formdata
. S'il n'y a aucune correspondance, l'analyseur de réponse utilise par défaut text
. Vous pouvez spécifier une valeur par défaut en définissant la touche spéciale */*
.
{
"application/json" : " json " ,
"multipart/form-data" : " formdata " ,
"*/*" : " text "
} ;
Toute valeur que vous spécifiez dans responseParserMap
est fusionnée dans la carte par défaut. C'est-à-dire que vous pouvez remplacer les valeurs par défaut et/ou ajouter de nouvelles valeurs.
Disons, par exemple, que vous souhaitez télécharger une image dans un blob. Vous pouvez définir la baseURL
sur votre point de terminaison API et un responseParserMap
qui téléchargera des images de tout type sous forme de blobs, mais autorisera toujours les téléchargements json
(car il s'agit de la valeur par défaut pour un content-type: application/json
).
import thwack from 'thwack' ;
thwack . defaults . responseParserMap = { 'image/*' : 'blob' } ;
Toute URL que vous téléchargez avec un type de contenu image/*
(par exemple image/jpeg
, image/png
, etc.) sera analysée avec l'analyseur blob
.
const getBlobUrl = async ( url ) => {
const blob = ( await thwack . get ( url ) ) . data ;
const objectURL = URL . createObjectURL ( blob ) ;
return objectURL ;
} ;
Voir cet exemple exécuté sur CodeSandbox.
Notez que vous pouvez utiliser cette technique pour autre chose que des images.
Comme vous pouvez le constater, l'utilisation responseParserMap
est un excellent moyen d'éliminer le besoin de définir responseType
pour différents appels Thwack.
validateStatus
Cette fonction facultative est utilisée pour déterminer les codes d'état que Thwack utilise pour renvoyer une promesse ou un lancement. Le status
de réponse lui est transmis. Si cette fonction renvoie la vérité, la promesse est résolue, sinon la promesse est rejetée.
La fonction par défaut lance tout statut qui n'est pas dans le 2xx (c'est-à-dire 200-299)
paramsSerializer
Il s'agit d'une fonction facultative que Thwack appellera pour sérialiser les params
. Par exemple, étant donné un objet {a:1, b:2, foo: 'bar'}
, il doit être sérialisé en chaîne a=1&b=2&foo=bar
.
Pour la plupart des gens, le sérialiseur par défaut devrait fonctionner correctement. Ceci est principalement pour le cas Edge et la compatibilité Axios.
Notez que le sérialiseur par défaut classe les paramètres par ordre alphabétique, ce qui constitue une bonne pratique à suivre. Si, toutefois, cela ne fonctionne pas dans votre situation, vous pouvez lancer votre propre sérialiseur.
resolver
Il s'agit d'une fonction que vous pouvez fournir pour remplacer le comportement par défaut du résolveur. resolver
prend deux arguments : une url
et une baseURL
qui doivent être indéfinies, ou une URL absolue. Il ne devrait y avoir aucune raison de remplacer le résolveur, mais c'est votre trappe de secours au cas où vous en auriez besoin.
status
Un number
représentant les codes d'état HTTP à 3 chiffres reçus.
ok
Un boolean
défini sur true est le code status
dans la plage 2xx (c'est-à-dire un succès). Cette valeur n'est pas affectée par validateStatus
.
statusText
Une string
représentant le texte du code status
. Vous devez utiliser le code status
(ou ok
) dans n'importe quelle logique de programme.
headers
Un objet clé/valeur avec les en-têtes HTTP renvoyés. Tous les en-têtes en double seront concaténés en un seul en-tête séparé par des points-virgules.
data
Cela conservera le corps renvoyé de la réponse HTTP après qu'elle ait été diffusée et convertie. La seule exception est si vous avez utilisé le responseType
de stream
, auquel cas data
sont définies directement sur l'élément body
.
Si une ThwackResponseError
a été levée, data
seront la représentation en texte brut du corps de la réponse.
options
Objet options
complet qui a traité la demande. Ces options
seront entièrement fusionnées avec toutes les instances parentes, ainsi qu'avec defaults
.
response
L'objet Response
HTTP complet tel que renvoyé par fetch
ou la response
d'un rappel d'événement synthétique.
Si la réponse d'une requête Thwack aboutit à un code status
non-2xx (par exemple 404 Not Found), alors une ThwackResponseError
est renvoyée.
Remarque : Il est possible que d'autres types d'erreurs soient générés (par exemple, un mauvais rappel de l'écouteur d'événement), il est donc recommandé d'interroger l'erreur détectée pour voir si elle est de type
ThwackResponseError
.
try {
const { data } = await thwack . get ( someUrl )
} catch ( ex ) {
if ( ex instanceof thwack . ThwackResponseError )
const { status , message } = ex ;
console . log ( `Thwack status ${ status } : ${ message } ` ) ;
} else {
throw ex ; // If not, rethrow the error
}
}
Une ThwackResponseError
possède toutes les propriétés d'une Error
JavaScript normale plus une propriété thwackResponse
avec les mêmes propriétés qu'un statut de réussite.
Les instances créées dans Thwack sont basées sur l'instance parent. Les options par défaut d'un parent sont transmises aux instances. Cela peut s'avérer utile pour configurer des options dans le parent pouvant affecter les enfants, telles que baseURL
,
Inversement, les parents peuvent utiliser addEventListener
pour surveiller leurs enfants (voir Comment enregistrer chaque appel d'API ci-dessous pour un exemple).
Combiné avec les instances, le système d'événements Thwack est ce qui rend Thwack extrêmement puissant. Avec lui, vous pouvez écouter différents événements.
Voici le flux des événements pour tous les événements. COMME vous pouvez le voir, il est possible que votre code entre dans une boucle sans fin, si votre rappel émet aveuglément une request()
sans vérifier si c'est déjà fait, alors soyez prudent.
request
Chaque fois qu'une partie de l'application appelle l'une des méthodes de récupération de données, un événement request
est déclenché. Tous les auditeurs obtiendront un objet ThwackRequestEvent
qui possède les options
de l'appel dans event.options
. Ces écouteurs d'événements peuvent faire quelque chose d'aussi simple que (enregistrer l'événement) ou aussi compliqué que d'empêcher la demande et de renvoyer une réponse avec (données simulées)
// callback will be called for every request made in Thwack
thwack . addEventListener ( 'request' , callback ) ;
Notez que les rappels peuvent être
async
vous permettant de différer Thwack afin que vous puissiez, par exemple, sortir et récupérer des données sur une URL différente avant de continuer.
response
L'événement est déclenché après la réception des en-têtes HTTP, mais avant que le corps ne soit diffusé et analysé. Les auditeurs recevront un objet ThwackResponseEvent
avec une clé thwackResponse
définie sur la réponse.
data
L'événement est déclenché une fois le corps diffusé et analysé. Il n'est déclenché que si la récupération a renvoyé un code d'état 2xx. Les auditeurs recevront un objet ThwackDataEvent
avec une clé thwackResponse
définie sur la réponse.
error
L'événement est déclenché une fois le corps diffusé et analysé. Il est déclenché si la récupération a renvoyé un code d'état non-2xx. Les auditeurs recevront un objet ThwackErrorEvent
avec une clé thwackResponse
définie sur la réponse.
Thwack fonctionnera sur NodeJS, mais nécessite un polyfill pour window.fetch
. Heureusement, il existe un merveilleux polyfill appelé node-fetch
que vous pouvez utiliser.
Si vous utilisez NodeJS version 10, vous aurez également besoin d'un polyfill pour Array#flat
et Object#fromEntries
. NodeJS version 11+ dispose de ces méthodes et ne nécessite pas de polyfill.
Vous pouvez soit fournir ces polyfills vous-même, soit utiliser l'une des importations pratiques suivantes à la place. Si vous exécutez NodeJS 11+, utilisez :
import thwack from 'thwack/node' ; // NodeJS version 12+
Si vous utilisez NodeJS 10, utilisez :
import thwack from 'thwack/node10' ; // NodeJS version 10
Si vous souhaitez fournir ces polyfills vous-même, alors pour utiliser Thwack, vous devez importer depuis thwack/core
et définir fetch
comme valeur par défaut pour fetch
.
import thwack from 'thwack/code' ;
thwack . defaults . fetch = global . fetch ;
Cela doit être fait dans le code de démarrage de votre application, généralement index.js
.
Remarque : Le
responseType
dublob
n'est pas pris en charge sur NodeJS.
Thwack est compatible avec React Native et ne nécessite aucun polyfill supplémentaire. Voir ci-dessous pour un exemple d'application écrite en React Native.
Remarque : React Native ne prend pas en charge
stream
en raison du #27741
Vous pouvez utiliser thwack.all()
et thwack.spread()
pour effectuer des requêtes simultanées. Les données sont ensuite présentées à votre rappel sous la forme d'un seul tableau.
Ici, nous affichons des informations pour deux utilisateurs de GitHub.
function displayGitHubUsers ( ) {
return thwack
. all ( [
thwack . get ( 'https://api.github.com/users/donavon' ) ,
thwack . get ( 'https://api.github.com/users/revelcw' ) ,
] )
. then (
thwack . spread ( ( ... results ) => {
const output = results
. map (
( { data } ) => ` ${ data . login } has ${ data . public_repos } public repos`
)
. join ( 'n' ) ;
console . log ( output ) ;
} )
) ;
}
Notez qu'il s'agit simplement de fonctions d'assistance. Si vous utilisez async
/ await
vous pouvez écrire ceci sans les assistants Thwack en utilisant Promise.all
.
async function displayGitHubUsers ( ) {
const results = await Promise . all ( [
thwack . get ( 'https://api.github.com/users/donavon' ) ,
thwack . get ( 'https://api.github.com/users/revelcw' ) ,
] ) ;
const output = results
. map ( ( { data } ) => ` ${ data . login } has ${ data . public_repos } public repos` )
. join ( 'n' ) ;
console . log ( output ) ;
}
Vous pouvez voir cela fonctionner en direct dans CodeSandbox.
(Démo inspirée de ce post blob sur axios/fetch)
Utilisez un AbortController
pour annuler les requêtes en passant son signal
dans les options thwack
.
Dans le navigateur, vous pouvez utiliser le AbortController intégré.
import thwack from 'thwack' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
Dans NodeJS, vous pouvez utiliser quelque chose comme abort-controller.
import thwack from 'thwack' ;
import AbortController from 'abort-controller' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
Si vous souhaitez effectuer une action lors de l'annulation de la demande, vous pouvez également écouter l'événement abort
au signal
:
signal . addEventListener ( 'abort' , handleAbort ) ;
Ajoutez un addEventListener('request', callback)
et enregistrez chaque demande dans la console.
import thwack from 'thwack' ;
thwack . addEventListener ( 'request' , ( event ) => {
console . log ( 'hitting URL' , thwack . getUri ( event . options ) ) ;
} ) ;
Si vous utilisez React, voici un Hook que vous pouvez « utiliser » dans votre application et qui accomplira la même chose.
import { useEffect } from 'react' ;
import thwack from 'thwack' ;
const logUrl = ( event ) => {
const { options } = event ;
const fullyQualifiedUrl = thwack . getUri ( options ) ;
console . log ( `hitting ${ fullyQualifiedUrl } ` ) ;
} ;
const useThwackLogger = ( ) => {
useEffect ( ( ) => {
thwack . addEventListener ( 'request' , logUrl ) ;
return ( ) => thwack . removeEventListener ( 'request' , logUrl ) ;
} , [ ] ) ;
} ;
export default useThwackLogger ;
Voici un extrait de code expliquant comment l'utiliser.
const App = ( ) = {
useThwackLogger ( )
return (
< div >
...
</ div >
)
}
Supposons que vous ayez une application qui a demandé certaines données utilisateur. Si l'application atteint une URL spécifique (par exemple users
) et demande un ID utilisateur particulier (par exemple 123
), vous souhaitez empêcher la demande d'atteindre le serveur et vous moquer des résultats.
L' status
dans ThwackResponse
est par défaut 200, donc à moins que vous n'ayez besoin de vous moquer d'une réponse non OK, il vous suffit de renvoyer data
.
thwack . addEventListener ( 'request' , async ( event ) => {
const { options } = event ;
if ( options . url === 'users' && options . params . id === 123 ) {
// tells Thwack to use the returned value instead of handling the event itself
event . preventDefault ( ) ;
// stop other listeners (if any) from further processing
event . stopPropagation ( ) ;
// because we called `preventDefault` above, the caller's request
// will be resolved to this `ThwackResponse` (defaults to status of 200 and ok)
return new thwack . ThwackResponse (
{
data : {
name : 'Fake Username' ,
email : '[email protected]' ,
} ,
} ,
options
) ;
}
} ) ;
Il est souvent souhaitable de convertir un DTO (Data Transfer Object) en quelque chose de plus facile à consommer par le client. Dans cet exemple ci-dessous, nous convertissons un DTO complexe en firstName
, lastName
, avatar
et email
. Les autres éléments de données renvoyés par l'appel d'API, mais non nécessaires aux applications, sont ignorés.
Vous pouvez voir un exemple de conversion DTO, de journalisation et de renvoi de fausses données dans cet exemple d'application.
Vous pouvez afficher le code source sur CodeSandbox.
Dans cet exemple, nous avons un React Hook qui charge une image en tant qu'URL Blob. Il met en cache l’URL vers le mappage d’URL Blob dans le stockage de session. Une fois chargée, toute actualisation de la page chargera instantanément l’image à partir de l’URL du Blob.
const useBlobUrl = ( imageUrl ) => {
const [ objectURL , setObjectURL ] = useState ( '' ) ;
useEffect ( ( ) => {
let url = sessionStorage . getItem ( imageUrl ) ;
async function fetchData ( ) {
if ( ! url ) {
const { data } = await thwack . get ( imageUrl , {
responseType : 'blob' ,
} ) ;
url = URL . createObjectURL ( data ) ;
sessionStorage . setItem ( imageUrl , url ) ;
}
setObjectURL ( url ) ;
}
fetchData ( ) ;
} , [ imageUrl ] ) ;
return objectURL ;
} ;
Voir cet exemple sur CodeSandbox
À l'heure actuelle, vous disposez d'un point de terminaison REST sur https://api.example.com
. Supposons que vous ayez publié un nouveau point de terminaison REST sur une URL différente et que vous souhaitiez commencer à acheminer lentement 2 % du trafic réseau vers ces nouveaux serveurs.
Remarque : normalement, cela serait géré par votre équilibreur de charge sur le back-end. Il est présenté ici à des fins de démonstration uniquement.
Nous pourrions y parvenir en remplaçant options.url
dans l'écouteur d'événement de demande comme suit.
thwack . addEventListener ( 'request' , ( event ) => {
if ( Math . random ( ) >= 0.02 ) {
return ;
}
// the code will be executed for approximately 2% of the requests
const { options } = event ;
const oldUrl = thwack . getUri ( options ) ;
const url = new URL ( '' , oldUrl ) ;
url . origin = 'https://api2.example.com' ; // point the origin at the new servers
const newUrl = url . href ; // Get the fully qualified URL
event . options = { ... event . options , url : newUrl } ; // replace `options`]
} ) ;
Avec use-thwack
, écrire une application de récupération de données pour React Native ne pourrait pas être plus simple.
Affichez l'intégralité de l'application exécutée sur Expo.
Thwack s'inspire fortement de l'Axios. Merci Matt !
Licence sous MIT
Merci à ces personnes merveilleuses (clé emoji) :
Donavon Ouest ? | Jeremy Tice | Yuraima Estévez | Jérémie Bargar | Brooke Scarlett Yalof | Karl Horky | Koji |
Tom Byrer | Ian Sutherland | Blake Yoder | Ryan Hinchey | Miro Dojkic | santicevic |
Ce projet suit la spécification de tous les contributeurs. Les contributions de toute nature sont les bienvenues !