
Thwack est :
Ce README est un travail en cours. Vous pouvez également me poser une question sur Twitter.
$ npm i thwackou
$ 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.
urlIl 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.
headersC'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'
defaultsCela 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 :
streamn'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.
headersUn 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
asyncvous 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
responseTypedublobn'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
streamen 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 !