Thwack é:
Este README é um trabalho em andamento. Você também pode me fazer uma pergunta no Twitter.
$ npm i thwack
ou
$ yarn add thwack
Axios era ótimo quando foi lançado naquela época. Isso nos deu um wrapper baseado em promessa em torno de XMLHttpRequest
, que era difícil de usar. Mas isso foi há muito tempo e os tempos mudaram – os navegadores ficaram mais inteligentes. Talvez seja hora de sua solução de busca de dados acompanhar?
O Thwack foi construído do zero com os navegadores modernos em mente. Por isso não tem a bagagem que o Axios tem. Axios pesa cerca de ~ 5k compactado. Thwack, por outro lado, é fino ~ 1,5k.
Eles suportam a mesma API, mas existem algumas diferenças – principalmente em torno options
– mas na maioria das vezes, eles devem poder ser usados de forma intercambiável para muitos aplicativos.
Thwack não tenta resolver todos os problemas, como faz o Axios, mas fornece a solução para 98% do que os usuários realmente precisam. Isso é o que dá ao Thwack sua pegada leve como uma pena.
Raspe isso. Thwack fornece o mesmo nível de potência que Axios com uma área ocupada muito menor. E o sistema de eventos baseado em promessas do Thwack é mais fácil de usar.
Os métodos a seguir estão disponíveis em todas as instâncias do 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;
O método create
cria (da!) uma nova instância filha da instância atual do Thwack com as options
fornecidas.
thwack.getUri(options: ThwackOptions): string;
A resolução de URL do Thwacks é compatível com RFC-3986. O de Axios não é. É desenvolvido por @thwack/resolve
.
Thwack oferece suporte aos seguintes tipos de eventos: request
, response
, data
e error
.
Para obter mais informações sobre o sistema de eventos do Thwack, consulte Eventos Thwack abaixo.
thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
Thwack possui as seguintes funções auxiliares para fazer solicitações simultâneas. Eles são principalmente para compatibilidade com Axios. Consulte a seção "Como fazer" abaixo para ver um exemplo de uso.
thwack.all(Promise<ThwackResponse>[])
thwack.spread(callback<results>)
O argumento options
possui as seguintes propriedades.
url
Este é um URL totalmente qualificado ou relativo.
baseURL
Define uma URL base que será usada para construir uma URL totalmente qualificada a partir url
acima. Deve ser um URL absoluto ou undefined
. O padrão é origin
+ pathname
da página da web atual se estiver executando em um navegador ou undefined
no Node ou React Native.
Por exemplo, se você fez isso:
thwack ( 'foo' , {
baseURL : 'http://example.com' ,
} ) ;
o URL buscado será:
http://example.com/foo
method
Uma string contendo um dos seguintes métodos HTTP: get
, post
, put
, patch
, delete
ou head
.
data
Se o method
for post
, put
ou patch
, esses serão os dados que serão usados para construir o corpo da solicitação.
headers
É aqui que você pode colocar qualquer cabeçalho de solicitação HTTP opcional. Qualquer cabeçalho especificado aqui será mesclado com quaisquer valores de cabeçalho de instância.
Por exemplo, se definirmos uma instância Thwack assim:
const api = thwack . create ( {
headers : {
'x-app-name' : 'My Awesome App' ,
} ,
} ) ;
Mais tarde, ao usar a instância, você faz uma chamada como esta:
const { data } = await api . get ( 'foo' , {
headers : {
'some-other-header' : 'My Awesome App' ,
} ,
} ) ;
Os cabeçalhos que seriam enviados são:
x-app-name: My Awesome App
some-other-header': 'My Awesome App'
defaults
Isso permite que você leia/defina as opções padrão para esta instância e, na verdade, para quaisquer instâncias filhas.
Exemplo:
thwack . defaults . baseURL = 'https://example.com/api' ;
Por exemplo, defaults
é o mesmo objeto passado para create
. Por exemplo, a saída a seguir será "https://example.com/api".
const instance = thwack . create ( {
baseURL : 'https://example.com/api' ,
} ) ;
console . log ( instance . defaults . baseURL ) ;
Observe também que definir defaults
em uma instância (ou mesmo passar options
) para uma instância NÃO afeta o pai. Portanto, para o exemplo a seguir, thwack.defaults.baseURL
ainda será "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 é um objeto opcional que contém os pares chave/valor que serão usados para construir a URL de busca. Se houver algum segmento :key
do baseURL
ou do url
, eles serão substituídos pelo valor da chave correspondente. Por exemplo, se você fez isso:
thwack ( 'orders/:id' , {
params : { id : 123 } ,
baseURL : 'http://example.com' ,
} ) ;
o URL buscado será:
http://example.com/orders/123
Se você não especificar um :name
, ou se houver mais param
s do que :name
s, então as chaves/valores restantes serão definidos como parâmetros de pesquisa (ou seja, ?key=value
).
maxDepth
O nível máximo de solicitações recursivas que podem ser feitas em um callbck antes que Thwack gere um erro. Isso é usado para evitar que um retorno de chamada de evento cause um loop recursivo. Isso se ele emitir outra request
sem as proteções adequadas em vigor. Padrão = 3.
responseType
Por padrão, Thwack determinará automaticamente como decodificar os dados de resposta com base no valor do cabeçalho de resposta content-type
. No entanto, se o servidor responder com um valor incorreto, você poderá substituir o analisador definindo responseType
. Os valores válidos são arraybuffer
, document
(ou seja, formdata
), json
, text
, stream
e blob
. O padrão é automático.
O que é retornado por Thwack é determinado pela tabela a seguir. A coluna "método de busca" é o que é resolvido em data
. Se você não especificar um responseType
, Thwack determinará automaticamente o método de busca com base no content-type
e na tabela responseParserMap
(veja abaixo).
Tipo de conteúdo | responseType | método fetch |
---|---|---|
application/json | json | response.json() |
multipart/form-data | formdata | response.formData() |
text/event-stream | stream | devolve response.body como data sem processamento |
blob | response.blob() | |
arraybuffer | response.arrayBuffer() | |
*/* | text | response.text() |
Nota:
stream
não é compatível no momento no React Native devido a #27741
responseParserMap
Outra maneira útil de determinar qual analisador de resposta usar é com responseParserMap
. Ele permite configurar um mapeamento entre o content-type
resultante do cabeçalho de resposta e o tipo de analisador.
Thwack usa o seguinte mapa como padrão, que permite a decodificação json
e formdata
. Se não houver correspondências, o analisador de resposta será padronizado como text
. Você pode especificar um padrão definindo a tecla especial */*
.
{
"application/json" : " json " ,
"multipart/form-data" : " formdata " ,
"*/*" : " text "
} ;
Qualquer valor especificado em responseParserMap
é mesclado no mapa padrão. Isso quer dizer que você pode substituir os padrões e/ou adicionar novos valores.
Digamos, por exemplo, que você queira baixar uma imagem em um blob. Você pode definir o baseURL
para o endpoint da API e um responseParserMap
que baixará imagens de qualquer tipo como blobs, mas ainda permitirá downloads json
(já que este é o padrão para um content-type: application/json
).
import thwack from 'thwack' ;
thwack . defaults . responseParserMap = { 'image/*' : 'blob' } ;
Qualquer URL que você baixar com um tipo de conteúdo image/*
(por exemplo image/jpeg
, image/png
, etc) será analisado com o analisador blob
.
const getBlobUrl = async ( url ) => {
const blob = ( await thwack . get ( url ) ) . data ;
const objectURL = URL . createObjectURL ( blob ) ;
return objectURL ;
} ;
Veja este exemplo em execução no CodeSandbox.
Observe que você pode usar essa técnica para outras coisas além de imagens.
Como você pode ver, usar responseParserMap
é uma ótima maneira de eliminar a necessidade de definir responseType
para diferentes chamadas Thwack.
validateStatus
Esta função opcional é usada para determinar quais códigos de status Thwack usa para retornar uma promessa ou lançamento. É passado o status
de resposta. Se esta função retornar verdadeira, a promessa será resolvida, caso contrário, a promessa será rejeitada.
A função padrão é lançada para qualquer status que não esteja no 2xx (ou seja, 200-299)
paramsSerializer
Esta é uma função opcional que Thwack chamará para serializar os params
. Por exemplo, dado um objeto {a:1, b:2, foo: 'bar'}
, ele deve serializar para a string a=1&b=2&foo=bar
.
Para a maioria das pessoas, o serializador padrão deve funcionar perfeitamente. Isso é principalmente para casos extremos e compatibilidade com Axios.
Observe que o serializador padrão coloca os parâmetros em ordem alfabética, o que é uma boa prática a seguir. Se, no entanto, isso não funcionar para sua situação, você poderá criar seu próprio serializador.
resolver
Esta é uma função que você pode fornecer para substituir o comportamento padrão do resolvedor. resolver
recebe dois argumentos: um url
e um baseURL
que deve ser indefinido ou um URL absoluto. Deve haver poucos motivos para você substituir o resolvedor, mas esta é a sua saída de emergência caso você precise.
status
Um number
que representa os códigos de status HTTP de 3 dígitos que foram recebidos.
ok
Um boolean
definido como verdadeiro é o código status
no intervalo 2xx (ou seja, um sucesso). Este valor não é afetado por validateStatus
.
statusText
Uma string
que representa o texto do código status
. Você deve usar o código status
(ou ok
) em qualquer lógica de programa.
headers
Um objeto chave/valor com os cabeçalhos HTTP retornados. Quaisquer cabeçalhos duplicados serão concatenados em um único cabeçalho separado por ponto e vírgula.
data
Isso manterá o corpo retornado da resposta HTTP após ela ter sido transmitida e convertida. A única exceção é se você usou o responseType
de stream
, caso em que data
são definidos diretamente no elemento body
.
Se um ThwackResponseError
for lançado, data
serão a representação de texto simples do corpo da resposta.
options
O objeto options
completo que processou a solicitação. Essas options
serão totalmente mescladas com qualquer instância pai, bem como com defaults
.
response
O objeto HTTP Response
completo retornado pela fetch
ou a response
de um retorno de chamada de evento sintético.
Se a resposta de uma solicitação Thwack resultar em um código status
diferente de 2xx (por exemplo, 404 Not Found), um ThwackResponseError
será lançado.
Nota: É possível que outros tipos de erros sejam lançados (por exemplo, um retorno de chamada de ouvinte de evento inválido), portanto, é uma prática recomendada interrogar o erro detectado para ver se ele é do 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
}
}
Um ThwackResponseError
possui todas as propriedades de um Error
JavaScript normal, além de uma propriedade thwackResponse
com as mesmas propriedades de um status de sucesso.
As instâncias criadas no Thwack são baseadas na instância pai. As opções padrão de um pai são transmitidas pelas instâncias. Isso pode ser útil para configurar opções no pai que podem afetar os filhos, como baseURL
,
Inversamente, os pais podem usar addEventListener
para monitorar seus filhos (veja Como registrar todas as chamadas de API abaixo para ver um exemplo disso).
Combinado com instâncias, o sistema de eventos Thwack é o que torna o Thwack extremamente poderoso. Com ele, você pode ouvir diversos eventos.
Aqui está o fluxo de eventos para todos os eventos. COMO você pode ver, é possível que seu código entre em um loop infinito, caso seu retorno de chamada emita cegamente um request()
sem verificar se já foi feito, então tome cuidado.
request
Sempre que qualquer parte da aplicação chama um dos métodos de busca de dados, um evento request
é acionado. Qualquer ouvinte receberá um objeto ThwackRequestEvent
que possui as options
da chamada em event.options
. Esses ouvintes de eventos podem fazer algo tão simples como (registrar o evento) ou tão complicado quanto impedir a solicitação e retornar uma resposta com (dados simulados)
// callback will be called for every request made in Thwack
thwack . addEventListener ( 'request' , callback ) ;
Observe que os retornos de chamada podem ser
async
permitindo adiar o Thwack para que você possa, por exemplo, sair e buscar dados em um URL diferente antes de continuar.
response
O evento é acionado após o recebimento dos cabeçalhos HTTP, mas antes do corpo ser transmitido e analisado. Os ouvintes receberão um objeto ThwackResponseEvent
com uma chave thwackResponse
definida para a resposta.
data
O evento é disparado depois que o corpo é transmitido e analisado. Ele será acionado somente se a busca retornar um código de status 2xx. Os ouvintes receberão um objeto ThwackDataEvent
com uma chave thwackResponse
definida para a resposta.
error
O evento é disparado depois que o corpo é transmitido e analisado. Ele será acionado se a busca retornar um código de status diferente de 2xx. Os ouvintes receberão um objeto ThwackErrorEvent
com uma chave thwackResponse
definida para a resposta.
Thwack funcionará em NodeJS, mas requer um polyfill para window.fetch
. Felizmente, existe um polyfill maravilhoso chamado node-fetch
que você pode usar.
Se você estiver usando o NodeJS versão 10, também precisará de um polyfill para Array#flat
e Object#fromEntries
. O NodeJS versão 11+ possui esses métodos e não requer polyfill.
Você mesmo pode fornecer esses polyfills ou usar uma das seguintes importações de conveniência. Se você estiver executando o NodeJS 11+, use:
import thwack from 'thwack/node' ; // NodeJS version 12+
Se você estiver executando no NodeJS 10, use:
import thwack from 'thwack/node10' ; // NodeJS version 10
Se você mesmo deseja fornecer esses polyfills, para usar o Thwack, você deve importar do thwack/core
e definir fetch
como o padrão para fetch
.
import thwack from 'thwack/code' ;
thwack . defaults . fetch = global . fetch ;
Isso deve ser feito no código de inicialização do seu aplicativo, geralmente index.js
.
Observação: o
responseType
doblob
não é compatível com NodeJS.
Thwack é compatível com React Native e não precisa de polyfills adicionais. Veja abaixo um exemplo de aplicativo escrito em React Native.
Nota: React Native não suporta
stream
devido a #27741
Você pode usar thwack.all()
e thwack.spread()
para fazer solicitações simultâneas. Os dados são então apresentados ao seu retorno de chamada como uma matriz.
Aqui exibimos informações para dois usuários do 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 ) ;
} )
) ;
}
Observe que estas são simplesmente funções auxiliares. Se você estiver usando async
/ await
você pode escrever isso sem os ajudantes do Thwack usando 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 ) ;
}
Você pode ver isso rodando ao vivo no CodeSandbox.
(Demonstração inspirada nesta postagem de blob em axios/fetch)
Use um AbortController
para cancelar solicitações, passando seu signal
nas opções thwack
.
No navegador, você pode usar o AbortController integrado.
import thwack from 'thwack' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
No NodeJS, você pode 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 ( ) ;
Caso você queira realizar alguma ação no cancelamento da solicitação, você também pode ouvir o evento abort
no signal
:
signal . addEventListener ( 'abort' , handleAbort ) ;
Adicione um addEventListener('request', callback)
e registre cada solicitação no console.
import thwack from 'thwack' ;
thwack . addEventListener ( 'request' , ( event ) => {
console . log ( 'hitting URL' , thwack . getUri ( event . options ) ) ;
} ) ;
Se você estiver usando React, aqui está um Hook que você pode "usar" em seu aplicativo e que realizará a mesma coisa.
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 ;
Aqui está um trecho de código sobre como usá-lo.
const App = ( ) = {
useThwackLogger ( )
return (
< div >
...
</ div >
)
}
Digamos que você tenha um aplicativo que solicitou alguns dados do usuário. Se o aplicativo estiver acessando um URL específico (digamos users
) e consultando um ID de usuário específico (digamos 123
), você gostaria de evitar que a solicitação chegue ao servidor e, em vez disso, simule os resultados.
O status
no ThwackResponse
é padronizado como 200, portanto, a menos que você precise simular uma resposta não OK, você só precisa retornar 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
) ;
}
} ) ;
Muitas vezes é desejável converter um DTO (Data Transfer Object) em algo mais fácil de consumir pelo cliente. Neste exemplo abaixo, convertemos um DTO complexo em firstName
, lastName
, avatar
e email
. Outros elementos de dados retornados da chamada de API, mas não necessários aos aplicativos, são ignorados.
Você pode ver um exemplo de conversão de DTO, registro e retorno de dados falsos neste aplicativo de exemplo.
Você pode visualizar o código-fonte no CodeSandbox.
Neste exemplo, temos um React Hook que carrega uma imagem como uma URL Blob. Ele armazena em cache o mapeamento de URL para URL de Blob no armazenamento de sessão. Depois de carregada, qualquer atualização da página carregará instantaneamente a imagem do URL do 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 ;
} ;
Veja este exemplo no CodeSandbox
No momento você tem um endpoint REST em https://api.example.com
. Suponha que você publicou um novo endpoint REST em uma URL diferente e gostaria de começar a rotear lentamente 2% do tráfego de rede para esses novos servidores.
Observação: normalmente isso seria tratado pelo seu balanceador de carga no back-end. É mostrado aqui apenas para fins de demonstração.
Poderíamos fazer isso substituindo options.url
no ouvinte de evento de solicitação da seguinte maneira.
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 com use-thwack
, escrever um aplicativo de busca de dados para React Native não poderia ser mais fácil.
Veja todo o aplicativo em execução na Expo.
Thwack é fortemente inspirado nos Axios. Obrigado Matt!
Licenciado pelo MIT
Os agradecimentos vão para essas pessoas maravilhosas (chave emoji):
Donavon Oeste ? | Jeremy Tice | Yuraima Estevez | Jeremy Bargar | Brooke Scarlett Yalof | Carlos Horky | Koji |
Tom Byrer | Ian Sutherland | Blake Yoder | Ryan Hinchey | Miro Dojkic | Santicevic |
Este projeto segue a especificação de todos os contribuidores. Contribuições de qualquer tipo são bem-vindas!