Golpe es:
Este README es un trabajo en progreso. También puedes hacerme una pregunta en Twitter.
$ npm i thwack
o
$ yarn add thwack
Axios era genial cuando se lanzó en su día. Nos dio un contenedor basado en promesas en torno XMLHttpRequest
, que era difícil de usar. Pero eso fue hace mucho tiempo y los tiempos han cambiado: los navegadores se han vuelto más inteligentes. ¿Quizás es hora de que su solución de obtención de datos se mantenga al día?
Thwack se creó desde cero teniendo en cuenta los navegadores modernos. Debido a esto, no tiene el equipaje que tiene Axios. Axios pesa alrededor de ~5k comprimidos. Thwack, por otro lado, es delgado ~ 1.5k.
Admiten la misma API, pero existen algunas diferencias, principalmente en torno a options
, pero en su mayor parte, deberían poder usarse indistintamente para muchas aplicaciones.
Thwack no intenta resolver todos los problemas, como lo hace Axios, sino que proporciona la solución para el 98% de lo que los usuarios realmente necesitan. Esto es lo que le da a Thwack su huella ligera como una pluma.
Tacha eso. Thwack proporciona el mismo nivel de potencia que Axios con un tamaño mucho menor. Y el sistema de eventos basado en promesas de Thwack es más fácil de usar.
Los siguientes métodos están disponibles en todas las instancias de 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;
El método create
crea (¡da!) una nueva instancia secundaria de la instancia actual de Thwack con las options
dadas.
thwack.getUri(options: ThwackOptions): string;
La resolución de URL de Thwacks cumple con RFC-3986. El de Axios no lo es. Está impulsado por @thwack/resolve
.
Thwack admite los siguientes tipos de eventos: request
, response
, data
y error
.
Para obtener más información sobre el sistema de eventos de Thwack, consulte Eventos de Thwack a continuación.
thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
Thwack tiene las siguientes funciones auxiliares para realizar solicitudes simultáneas. Son principalmente para compatibilidad con Axios. Consulte la sección "Cómo hacer" a continuación para ver un ejemplo de uso.
thwack.all(Promise<ThwackResponse>[])
thwack.spread(callback<results>)
El argumento options
tiene las siguientes propiedades.
url
Esta es una URL totalmente calificada o relativa.
baseURL
Define una URL base que se utilizará para crear una URL completa a partir de url
anterior. Debe ser una URL absoluta o undefined
. El valor predeterminado es el origin
+ pathname
de la página web actual si se ejecuta en un navegador o undefined
en Node o React Native.
Por ejemplo, si hicieras esto:
thwack ( 'foo' , {
baseURL : 'http://example.com' ,
} ) ;
la URL recuperada será:
http://example.com/foo
method
Una cadena que contiene uno de los siguientes métodos HTTP: get
, post
, put
, patch
, delete
o head
.
data
Si el method
es post
, put
o patch
, estos son los datos que se utilizarán para crear el cuerpo de la solicitud.
headers
Aquí es donde puede colocar cualquier encabezado de solicitud HTTP opcional. Cualquier encabezado que especifique aquí se fusionará con cualquier valor de encabezado de instancia.
Por ejemplo, si configuramos una instancia de Thwack como esta:
const api = thwack . create ( {
headers : {
'x-app-name' : 'My Awesome App' ,
} ,
} ) ;
Luego, cuando usas la instancia, haces una llamada como esta:
const { data } = await api . get ( 'foo' , {
headers : {
'some-other-header' : 'My Awesome App' ,
} ,
} ) ;
Los encabezados que se enviarían son:
x-app-name: My Awesome App
some-other-header': 'My Awesome App'
defaults
Esto le permite leer/configurar las opciones predeterminadas para esta instancia y, de hecho, cualquier instancia secundaria.
Ejemplo:
thwack . defaults . baseURL = 'https://example.com/api' ;
Por ejemplo, defaults
es el mismo objeto pasado para create
. Por ejemplo, lo siguiente generará "https://example.com/api".
const instance = thwack . create ( {
baseURL : 'https://example.com/api' ,
} ) ;
console . log ( instance . defaults . baseURL ) ;
También tenga en cuenta que establecer defaults
en una instancia (o incluso pasar options
) a una instancia NO afecta al padre. Entonces, para el siguiente ejemplo, thwack.defaults.baseURL
seguirá siendo "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
Este es un objeto opcional que contiene los pares clave/valor que se utilizarán para crear la URL de recuperación. Si hay segmentos :key
de baseURL
o url
, se reemplazarán con el valor de la clave coincidente. Por ejemplo, si hicieras esto:
thwack ( 'orders/:id' , {
params : { id : 123 } ,
baseURL : 'http://example.com' ,
} ) ;
la URL recuperada será:
http://example.com/orders/123
Si no especifica :name
, o hay más param
que :name
s, entonces las claves/valores restantes se establecerán como parámetros de búsqueda (es decir ?key=value
).
maxDepth
El nivel máximo de solicitudes recursivas que se pueden realizar en una callbck antes de que Thwack arroje un error. Esto se utiliza para evitar que una devolución de llamada de evento provoque un bucle recursivo, esto si emite otra request
sin las medidas de seguridad adecuadas. Predeterminado = 3.
responseType
De forma predeterminada, Thwack determinará automáticamente cómo decodificar los datos de respuesta en función del valor del content-type
del encabezado de respuesta. Sin embargo, si el servidor responde con un valor incorrecto, puede anular el analizador configurando responseType
. Los valores válidos son arraybuffer
, document
(es decir, formdata
), json
, text
, stream
y blob
. El valor predeterminado es automático.
Lo que devuelve Thwack está determinado por la siguiente tabla. La columna "método de recuperación" es la que se resuelve en data
. Si no especifica un responseType
, Thwack determinará automáticamente el método de recuperación según content-type
y la tabla responseParserMap
(ver más abajo).
Tipo de contenido | responseType | método fetch |
---|---|---|
application/json | json | response.json() |
multipart/form-data | formdata | response.formData() |
text/event-stream | stream | devuelve response.body como data sin procesar |
blob | response.blob() | |
arraybuffer | response.arrayBuffer() | |
*/* | text | response.text() |
Nota:
stream
actualmente no es compatible con React Native debido al n.° 27741
responseParserMap
Otra forma útil de determinar qué analizador de respuestas utilizar es con responseParserMap
. Le permite configurar una asignación entre el content-type
resultante del encabezado de respuesta y el tipo de analizador.
Thwack utiliza el siguiente mapa como predeterminado, que permite la decodificación json
y formdata
. Si no hay coincidencias, el analizador de respuestas utiliza de forma predeterminada text
. Puede especificar un valor predeterminado configurando la tecla especial */*
.
{
"application/json" : " json " ,
"multipart/form-data" : " formdata " ,
"*/*" : " text "
} ;
Cualquier valor que especifique en responseParserMap
se fusiona en el mapa predeterminado. Es decir, puede anular los valores predeterminados y/o agregar nuevos valores.
Digamos, por ejemplo, que desea descargar una imagen en un blob. Puede configurar la baseURL
para el punto final de su API y un responseParserMap
que descargará imágenes de cualquier tipo como blobs, pero aún permitirá descargas json
(ya que este es el valor predeterminado para un content-type: application/json
).
import thwack from 'thwack' ;
thwack . defaults . responseParserMap = { 'image/*' : 'blob' } ;
Cualquier URL que descargue con un tipo de contenido image/*
(por ejemplo, image/jpeg
, image/png
, etc.) se analizará con el analizador blob
.
const getBlobUrl = async ( url ) => {
const blob = ( await thwack . get ( url ) ) . data ;
const objectURL = URL . createObjectURL ( blob ) ;
return objectURL ;
} ;
Vea este ejemplo ejecutándose en CodeSandbox.
Tenga en cuenta que puede utilizar esta técnica para otras cosas además de las imágenes.
Como puede ver, usar responseParserMap
es una excelente manera de eliminar la necesidad de configurar responseType
para diferentes llamadas de Thwack.
validateStatus
Esta función opcional se usa para determinar qué códigos de estado usa Thwack para devolver una promesa o un lanzamiento. Se pasa el status
de respuesta. Si esta función devuelve verdad, la promesa se resuelve; de lo contrario, la promesa se rechaza.
La función predeterminada genera cualquier estado que no esté en 2xx (es decir, 200-299)
paramsSerializer
Esta es una función opcional que Thwack llamará para serializar los params
. Por ejemplo, dado un objeto {a:1, b:2, foo: 'bar'}
, debe serializarse en la cadena a=1&b=2&foo=bar
.
Para la mayoría de las personas, el serializador predeterminado debería funcionar bien. Esto se debe principalmente a la compatibilidad con casos extremos y Axios.
Tenga en cuenta que el serializador predeterminado ordena alfabéticamente los parámetros, lo cual es una buena práctica a seguir. Sin embargo, si esto no funciona en su situación, puede ejecutar su propio serializador.
resolver
Esta es una función que puede proporcionar para anular el comportamiento de resolución predeterminado. resolver
toma dos argumentos: una url
y una baseURL
que no debe estar definida o una URL absoluta. Debería haber pocas razones para reemplazar el resolutor, pero esta es su vía de escape en caso de que sea necesario.
status
Un number
que representa los códigos de estado HTTP de 3 dígitos que se recibieron.
ok
Un boolean
establecido en verdadero es el código status
en el rango 2xx (es decir, un éxito). Este valor no se ve afectado por validateStatus
.
statusText
Una string
que representa el texto del código status
. Debe utilizar el código status
(o ok
) en cualquier lógica de programa.
headers
Un objeto clave/valor con los encabezados HTTP devueltos. Cualquier encabezado duplicado se concatenará en un único encabezado separado por punto y coma.
data
Esto contendrá el cuerpo devuelto de la respuesta HTTP después de que se haya transmitido y convertido. La única excepción es si utilizó el responseType
de stream
, en cuyo caso data
se establecen directamente en el elemento body
.
Si se generó un ThwackResponseError
, data
serán la representación de texto sin formato del cuerpo de la respuesta.
options
El objeto options
completo que procesó la solicitud. Estas options
se fusionarán completamente con cualquier instancia principal, así como con defaults
.
response
El objeto Response
HTTP completo devuelto por fetch
o la response
de una devolución de llamada de evento sintético.
Si la respuesta de una solicitud Thwack da como resultado un código status
distinto de 2xx (por ejemplo, 404 No encontrado), se genera un ThwackResponseError
.
Nota: Es posible que se produzcan otros tipos de errores (por ejemplo, una devolución de llamada de escucha de eventos incorrecta), por lo que es una buena práctica interrogar el error detectado para ver si es del tipo
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
}
}
Un ThwackResponseError
tiene todas las propiedades de un Error
de JavaScript normal más una propiedad thwackResponse
con las mismas propiedades que un estado de éxito.
Las instancias creadas en Thwack se basan en la instancia principal. Las opciones predeterminadas de un padre se transmiten a través de las instancias. Esto puede resultar útil para configurar opciones en el padre que pueden afectar a los hijos, como baseURL
,
A la inversa, los padres pueden usar addEventListener
para monitorear a sus hijos (consulte Cómo registrar cada llamada API a continuación para ver un ejemplo de esto).
Combinado con instancias, el sistema de eventos Thwack es lo que hace que Thwack sea extremadamente poderoso. Con él, puedes escuchar diferentes eventos.
Aquí está el flujo de eventos para todos los eventos. Como puede ver, es posible que su código entre en un bucle sin fin, si su devolución de llamada emite ciegamente una request()
sin verificar si ya lo ha hecho, así que tenga cuidado.
request
Cada vez que cualquier parte de la aplicación llama a uno de los métodos de obtención de datos, se activa un evento request
. Cualquier oyente obtendrá un objeto ThwackRequestEvent
que tiene las options
de llamada en event.options
. Estos detectores de eventos pueden hacer algo tan simple como (registrar el evento) o tan complicado como prevenir la solicitud y devolver una respuesta con (datos simulados).
// callback will be called for every request made in Thwack
thwack . addEventListener ( 'request' , callback ) ;
Tenga en cuenta que las devoluciones de llamada pueden ser
async
lo que le permite posponer Thwack para que pueda, por ejemplo, salir y buscar datos en una URL diferente antes de continuar.
response
El evento se activa después de recibir los encabezados HTTP, pero antes de transmitir y analizar el cuerpo. Los oyentes recibirán un objeto ThwackResponseEvent
con una clave thwackResponse
configurada para la respuesta.
data
El evento se activa después de que el cuerpo se transmite y analiza. Se activa solo si la recuperación devolvió un código de estado 2xx. Los oyentes recibirán un objeto ThwackDataEvent
con una clave thwackResponse
configurada para la respuesta.
error
El evento se activa después de que el cuerpo se transmite y analiza. Se activa si la recuperación devolvió un código de estado distinto de 2xx. Los oyentes recibirán un objeto ThwackErrorEvent
con una clave thwackResponse
configurada para la respuesta.
Thwack funcionará en NodeJS, pero requiere un polyfill para window.fetch
. Afortunadamente, existe un maravilloso polyfill llamado node-fetch
que puedes usar.
Si está utilizando NodeJS versión 10, también necesitará un polyfill para Array#flat
y Object#fromEntries
. NodeJS versión 11+ tiene estos métodos y no requiere un polyfill.
Puede proporcionar estos polyfills usted mismo o utilizar una de las siguientes importaciones convenientes. Si está ejecutando NodeJS 11+, use:
import thwack from 'thwack/node' ; // NodeJS version 12+
Si está ejecutando NodeJS 10, use:
import thwack from 'thwack/node10' ; // NodeJS version 10
Si desea proporcionar estos polyfills usted mismo, para usar Thwack, debe importar desde thwack/core
y configurar fetch
como el valor predeterminado para fetch
.
import thwack from 'thwack/code' ;
thwack . defaults . fetch = global . fetch ;
Esto debe hacerse en el código de inicio de su aplicación, generalmente index.js
.
Nota: El tipo de
responseType
deblob
no se admite en NodeJS.
Thwack es compatible con React Native y no necesita polyfills adicionales. Consulte a continuación una aplicación de muestra escrita en React Native.
Nota: React Native no admite
stream
debido al número 27741
Puede utilizar thwack.all()
y thwack.spread()
para realizar solicitudes simultáneas. Luego, los datos se presentan a su devolución de llamada como una matriz.
Aquí mostramos información para dos usuarios 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 ) ;
} )
) ;
}
Tenga en cuenta que estas son simplemente funciones auxiliares. Si está utilizando async
/ await
puede escribir esto sin que los ayudantes de Thwack utilicen 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 ) ;
}
Puede ver esto ejecutándose en vivo en CodeSandbox.
(Demostración inspirada en esta publicación de blob en axios/fetch)
Utilice un AbortController
para cancelar solicitudes pasando su signal
en las opciones thwack
.
En el navegador, puede utilizar el AbortController integrado.
import thwack from 'thwack' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
En NodeJS, puedes usar algo como 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 ( ) ;
En caso de que desee realizar alguna acción en la cancelación de la solicitud, también puede escuchar el evento abort
en signal
:
signal . addEventListener ( 'abort' , handleAbort ) ;
Agregue un addEventListener('request', callback)
y registre cada solicitud en la consola.
import thwack from 'thwack' ;
thwack . addEventListener ( 'request' , ( event ) => {
console . log ( 'hitting URL' , thwack . getUri ( event . options ) ) ;
} ) ;
Si estás usando React, aquí tienes un Hook que puedes "usar" en tu aplicación y que logrará lo mismo.
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 ;
Aquí hay un fragmento de código sobre cómo usarlo.
const App = ( ) = {
useThwackLogger ( )
return (
< div >
...
</ div >
)
}
Supongamos que tiene una aplicación que ha solicitado algunos datos de usuario. Si la aplicación accede a una URL específica (por ejemplo, users
) y solicita una ID de usuario en particular (por ejemplo, 123
), le gustaría evitar que la solicitud llegue al servidor y, en su lugar, burlarse de los resultados.
El status
en ThwackResponse
por defecto es 200, por lo que, a menos que necesite simular una respuesta que no es correcta, solo necesita devolver 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
) ;
}
} ) ;
A menudo es deseable convertir un DTO (objeto de transferencia de datos) en algo más fácil de consumir por parte del cliente. En el siguiente ejemplo, convertimos un DTO complejo en firstName
, lastName
, avatar
y email
. Se ignoran otros elementos de datos que se devuelven desde la llamada API, pero que las aplicaciones no necesitan.
Puede ver un ejemplo de conversión DTO, registro y devolución de datos falsos en esta aplicación de muestra.
Puede ver el código fuente en CodeSandbox.
En este ejemplo, tenemos un React Hook que carga una imagen como una URL de Blob. Almacena en caché la asignación de URL a Blob URL en el almacenamiento de la sesión. Una vez cargada, cualquier actualización de la página cargará instantáneamente la imagen desde Blob URL.
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 ;
} ;
Vea este ejemplo en CodeSandbox
Ahora tiene un punto final REST en https://api.example.com
. Suponga que ha publicado un nuevo punto final REST en una URL diferente y le gustaría comenzar a enrutar lentamente el 2 % del tráfico de la red a estos nuevos servidores.
Nota: normalmente esto lo manejaría su balanceador de carga en el back-end. Se muestra aquí sólo con fines de demostración.
Podríamos lograr esto reemplazando options.url
en el detector de eventos de solicitud de la siguiente manera.
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`]
} ) ;
Junto con use-thwack
, escribir una aplicación de recuperación de datos para React Native no podría ser más fácil.
Vea la aplicación completa ejecutándose en Expo.
Thwack está fuertemente inspirado en los Axios. Gracias matt!
Con licencia del MIT
Gracias a estas maravillosas personas (tecla emoji):
Donavon Oeste ? | Jeremy Tice | Yuraima Estévez | Jeremy Bargar | Brooke Scarlett Yalof | Karl Horky | koji |
Tom Byrer | Ian Sutherland | Yoder Blake | Ryan Hinchey | Miró Dojkic | santicevic |
Este proyecto sigue la especificación de todos los contribuyentes. ¡Bienvenidos aportes de cualquier tipo!