Твак - это:
Этот README находится в стадии разработки. Вы также можете задать мне вопрос в Твиттере.
$ npm i thwack
или
$ yarn add thwack
Axios был великолепен, когда был выпущен в свое время. Это дало нам основанную на обещаниях обертку вокруг XMLHttpRequest
, которую было сложно использовать. Но это было давно и времена изменились — браузеры стали умнее. Может быть, пришло время вашему решению для получения данных не отставать?
Thwack был создан с нуля с учетом современных браузеров. Из-за этого у него нет того багажа, который есть у Axios. Axios весит около 5 тыс. в сжатом виде. С другой стороны, у Thwack небольшая цифра ~1,5 тыс.
Они поддерживают один и тот же API, но есть некоторые различия — в основном в options
— но по большей части их можно использовать как взаимозаменяемые для многих приложений.
Thwack не пытается решить все проблемы, как это делает Axios, а вместо этого предоставляет решение для 98% того, что действительно нужно пользователям. Именно это делает Thwack таким легким, как перышко.
Поцарапайте это. Thwack обеспечивает тот же уровень мощности, что и Axios, но занимает гораздо меньше места. А систему событий Thwack, основанную на обещаниях, проще использовать.
Следующие методы доступны во всех экземплярах 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;
Метод create
создает (да!) новый дочерний экземпляр текущего экземпляра Thwack с заданными options
.
thwack.getUri(options: ThwackOptions): string;
Разрешение URL-адресов Thwacks соответствует RFC-3986. У Аксиоса нет. Он работает на @thwack/resolve
.
Thwack поддерживает следующие типы событий: request
, response
, data
и error
.
Дополнительную информацию о системе событий Thwack см. в разделе «События Thwack» ниже.
thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;
Thwack имеет следующие вспомогательные функции для выполнения одновременных запросов. В основном они предназначены для совместимости с Axios. Пример использования см. в разделе «Как сделать» ниже.
thwack.all(Promise<ThwackResponse>[])
thwack.spread(callback<results>)
Аргумент options
имеет следующие свойства.
url
Это либо полный, либо относительный URL-адрес.
baseURL
Определяет базовый URL-адрес, который будет использоваться для создания полного URL-адреса на основе приведенного выше URL- url
. Должен быть абсолютный URL-адрес или undefined
. По умолчанию используется origin
+ pathname
к текущей веб-странице, если она запущена в браузере или undefined
в Node или React Native.
Например, если вы сделали это:
thwack ( 'foo' , {
baseURL : 'http://example.com' ,
} ) ;
полученный URL-адрес будет:
http://example.com/foo
method
Строка, содержащая один из следующих методов HTTP: get
, post
, put
, patch
, delete
или head
.
data
Если используется method
post
, put
или patch
, это данные, которые будут использоваться для построения тела запроса.
headers
Здесь вы можете разместить любые дополнительные заголовки HTTP-запроса. Любой указанный здесь заголовок объединяется с любыми значениями заголовка экземпляра.
Например, если мы установим экземпляр Thwack следующим образом:
const api = thwack . create ( {
headers : {
'x-app-name' : 'My Awesome App' ,
} ,
} ) ;
Затем, когда вы используете экземпляр, вы делаете такой вызов:
const { data } = await api . get ( 'foo' , {
headers : {
'some-other-header' : 'My Awesome App' ,
} ,
} ) ;
Заголовки, которые будут отправлены:
x-app-name: My Awesome App
some-other-header': 'My Awesome App'
defaults
Это позволяет вам читать/устанавливать параметры по умолчанию для этого экземпляра и, по сути, для любых дочерних экземпляров.
Пример:
thwack . defaults . baseURL = 'https://example.com/api' ;
Для экземпляра defaults
— это тот же объект, который был передан в create
. Например, следующее выведет «https://example.com/api».
const instance = thwack . create ( {
baseURL : 'https://example.com/api' ,
} ) ;
console . log ( instance . defaults . baseURL ) ;
Также обратите внимание, что установка defaults
для экземпляра (или даже передача options
) экземпляру НЕ влияет на родителя. Итак, в следующем примере thwack.defaults.baseURL
по-прежнему будет «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
Это необязательный объект, содержащий пары ключ/значение, которые будут использоваться для создания URL-адреса выборки. Если есть какие-либо сегменты :key
в baseURL
или url
, они будут заменены значением соответствующего ключа. Например, если вы сделали это:
thwack ( 'orders/:id' , {
params : { id : 123 } ,
baseURL : 'http://example.com' ,
} ) ;
полученный URL-адрес будет:
http://example.com/orders/123
Если вы не укажете :name
или имеется больше param
, чем :name
, то оставшиеся ключ/значения будут установлены как параметры поиска (т.е. ?key=value
).
maxDepth
Максимальный уровень рекурсивных запросов, которые можно выполнить в callbck, прежде чем Thwack выдаст ошибку. Это используется для предотвращения возникновения рекурсивного цикла при обратном вызове события. Это происходит, если он выдает другой request
без надлежащих мер безопасности. По умолчанию = 3.
responseType
По умолчанию Thwack автоматически определит, как декодировать данные ответа, на основе значения content-type
заголовка ответа. Однако, если сервер отвечает неверным значением, вы можете переопределить синтаксический анализатор, установив responseType
. Допустимые значения: arraybuffer
, document
(т. е. formdata
), json
, text
, stream
и blob
. По умолчанию установлено автоматическое.
То, что возвращает Thwack, определяется следующей таблицей. Столбец «метод выборки» — это то, что разрешается в data
. Если вы не укажете responseType
, Thwack автоматически определит метод выборки на основе content-type
и таблицы responseParserMap
(см. ниже).
Тип контента | responseType | метод fetch |
---|---|---|
application/json | json | response.json() |
multipart/form-data | formdata | response.formData() |
text/event-stream | stream | передает ответ response.body как data без обработки |
blob | response.blob() | |
arraybuffer | response.arrayBuffer() | |
*/* | text | response.text() |
Примечание. В настоящее время
stream
не поддерживается в React Native из-за #27741.
responseParserMap
Еще один полезный способ определить, какой анализатор ответа использовать, — это responseParserMap
. Это позволяет вам настроить сопоставление между результирующим content-type
из заголовка ответа и типом анализатора.
Thwack использует следующую карту по умолчанию, которая позволяет декодировать json
и formdata
. Если совпадений нет, анализатор ответа по умолчанию использует text
. Вы можете указать значение по умолчанию, задав специальный ключ */*
.
{
"application/json" : " json " ,
"multipart/form-data" : " formdata " ,
"*/*" : " text "
} ;
Любое значение, указанное вами в responseParserMap
объединяется с картой по умолчанию. То есть вы можете переопределить значения по умолчанию и/или добавить новые значения.
Допустим, например, вы хотите загрузить изображение в большой двоичный объект. Вы можете установить baseURL
для конечной точки вашего API и responseParserMap
, который будет загружать изображения любого типа в виде больших двоичных объектов, но по-прежнему будет разрешать загрузку json
(поскольку это значение по умолчанию для content-type: application/json
).
import thwack from 'thwack' ;
thwack . defaults . responseParserMap = { 'image/*' : 'blob' } ;
Любой URL-адрес, который вы загружаете с типом контента image/*
(например, image/jpeg
, image/png
и т. д.), будет анализироваться с помощью анализатора blob
.
const getBlobUrl = async ( url ) => {
const blob = ( await thwack . get ( url ) ) . data ;
const objectURL = URL . createObjectURL ( blob ) ;
return objectURL ;
} ;
См. этот пример, работающий в CodeSandbox.
Обратите внимание, что вы можете использовать эту технику не только для изображений.
Как видите, использование responseParserMap
— отличный способ избавиться от необходимости устанавливать responseType
для разных вызовов Thwack.
validateStatus
Эта дополнительная функция используется для определения того, какие коды состояния использует Thwack для возврата обещания или броска. Ему передается status
ответа. Если эта функция возвращает true, обещание разрешено, в противном случае обещание отклонено.
Функция по умолчанию выдает любой статус, отличный от 2xx (т.е. 200-299).
paramsSerializer
Это необязательная функция, которую Thwack будет вызывать для сериализации params
. Например, для данного объекта {a:1, b:2, foo: 'bar'}
он должен сериализоваться в строку a=1&b=2&foo=bar
.
Для большинства людей сериализатор по умолчанию должен работать нормально. В основном это касается краевого случая и совместимости с Axios.
Обратите внимание, что сериализатор по умолчанию упорядочивает параметры в алфавитном порядке, что является хорошей практикой. Однако если в вашей ситуации это не работает, вы можете создать собственный сериализатор.
resolver
Это функция, которую вы можете предоставить для переопределения поведения преобразователя по умолчанию. resolver
принимает два аргумента: url
и baseURL
, который должен быть неопределенным, или абсолютный URL-адрес. У вас не должно быть особых причин заменять резольвер, но это ваш запасной выход на случай, если вам понадобится.
status
number
представляющее полученные трехзначные коды состояния HTTP.
ok
boolean
значение, равное true, представляет собой код status
в диапазоне 2xx (т. е. успех). На это значение не влияет validateStatus
.
statusText
string
представляющая текст кода status
. Вы должны использовать код status
(или ok
) в любой логике программы.
headers
Объект «ключ-значение» с возвращаемыми заголовками HTTP. Любые повторяющиеся заголовки будут объединены в один заголовок, разделенный точкой с запятой.
data
Здесь будет храниться возвращенное тело HTTP-ответа после его потоковой передачи и преобразования. Единственное исключение — если вы использовали responseType
stream
, и в этом случае data
устанавливаются непосредственно в элементе body
.
Если был выдан ThwackResponseError
, data
будут представлять собой простое текстовое представление тела ответа.
options
Полный объект options
, который обработал запрос. Эти options
будут полностью объединены с любыми родительскими экземплярами, а также со defaults
.
response
Полный объект HTTP- Response
, возвращенный при fetch
, или response
от обратного вызова синтетического события.
Если ответ на запрос Thwack приводит к коду status
отличному от 2xx (например, 404 Not Found), то выдается ошибка ThwackResponseError
.
Примечание. Вполне возможно, что могут быть выданы другие типы ошибок (например, обратный вызов прослушивателя неверных событий), поэтому рекомендуется опрашивать пойманную ошибку, чтобы определить, относится ли она к типу
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
}
}
ThwackResponseError
имеет все свойства обычной Error
JavaScript, а также свойство thwackResponse
с теми же свойствами, что и статус успеха.
Экземпляры, созданные в Thwack, основаны на родительском экземпляре. Параметры родителя по умолчанию передаются через экземпляры. Это может пригодиться для настройки параметров родительского элемента, которые могут повлиять на дочерние элементы, например baseURL
,
И наоборот, родители могут использовать addEventListener
для мониторинга своих детей (пример этого см. в разделе «Как регистрировать каждый вызов API» ниже).
В сочетании с инстансами система событий Thwack делает Thwack чрезвычайно мощным. С его помощью вы можете прослушивать разные события.
Вот поток событий для всех событий. КАК вы можете видеть, ваш код может попасть в бесконечный цикл, если ваш обратный вызов вслепую выполнит request()
не проверив, было ли это уже сделано, поэтому будьте осторожны.
request
Всякий раз, когда какая-либо часть приложения вызывает один из методов выборки данных, запускается событие request
. Любые слушатели получат объект ThwackRequestEvent
, options
вызова которого указаны в event.options
. Эти прослушиватели событий могут делать что-то простое (зарегистрировать событие) или столь же сложное, как предотвращение запроса и возврат ответа с помощью (фиктивных данных).
// callback will be called for every request made in Thwack
thwack . addEventListener ( 'request' , callback ) ;
Обратите внимание, что обратные вызовы могут быть
async
что позволяет вам отложить Thwack, чтобы вы могли, например, выйти и получить данные по другому URL-адресу, прежде чем продолжить.
response
событие Событие вызывается после получения заголовков HTTP, но до потоковой передачи и анализа тела. Слушатели получат объект ThwackResponseEvent
с ключом thwackResponse
установленным для ответа.
data
Событие вызывается после потоковой передачи и анализа тела. Он запускается только в том случае, если выборка вернула код состояния 2xx. Слушатели получат объект ThwackDataEvent
с ключом thwackResponse
установленным для ответа.
error
Событие вызывается после потоковой передачи и анализа тела. Он запускается, если выборка вернула код состояния, отличный от 2xx. Слушатели получат объект ThwackErrorEvent
с ключом thwackResponse
установленным для ответа.
Thwack будет работать на NodeJS, но для window.fetch
требуется полифилл. К счастью, вы можете использовать замечательный полифил под названием node-fetch
.
Если вы используете NodeJS версии 10, вам также понадобится полифилл для Array#flat
и Object#fromEntries
. NodeJS версии 11+ имеет эти методы и не требует полифилла.
Вы можете либо предоставить эти полифилы самостоятельно, либо вместо этого использовать один из следующих удобных импортов. Если вы используете NodeJS 11+, используйте:
import thwack from 'thwack/node' ; // NodeJS version 12+
Если вы используете NodeJS 10, используйте:
import thwack from 'thwack/node10' ; // NodeJS version 10
Если вы хотите предоставить эти полифилы самостоятельно, то для использования Thwack вам необходимо импортировать их из thwack/core
и установить fetch
по умолчанию для fetch
.
import thwack from 'thwack/code' ;
thwack . defaults . fetch = global . fetch ;
Это должно быть сделано в коде запуска вашего приложения, обычно index.js
.
Примечание. Тип
responseType
blob
не поддерживается в NodeJS.
Thwack совместим с React Native и не требует дополнительных полифилов. Ниже приведен пример приложения, написанного на React Native.
Примечание. React Native не поддерживает
stream
за #27741.
Вы можете использовать thwack.all()
и thwack.spread()
для одновременного выполнения запросов. Затем данные передаются вашему обратному вызову в виде одного массива.
Здесь мы отображаем информацию для двух пользователей 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 ) ;
} )
) ;
}
Обратите внимание, что это просто вспомогательные функции. Если вы используете async
/ await
вы можете написать это без помощников Thwack, используя 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 ) ;
}
Вы можете увидеть это вживую в CodeSandbox.
(Демо-версия, вдохновленная этим сообщением о blob на axios/fetch)
Используйте AbortController
для отмены запросов, передав его signal
в параметрах thwack
.
В браузере вы можете использовать встроенный AbortController.
import thwack from 'thwack' ;
const controller = new AbortController ( ) ;
const { signal } = controller ;
thwack ( url , { signal } ) . then ( handleResponse ) . catch ( handleError ) ;
controller . abort ( ) ;
В NodeJS вы можете использовать что-то вроде контроллера прерывания.
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 ( ) ;
Если вы хотите выполнить какое-либо действие при отмене запроса, вы также можете прослушать signal
abort
:
signal . addEventListener ( 'abort' , handleAbort ) ;
Добавьте addEventListener('request', callback)
и регистрируйте каждый запрос на консоли.
import thwack from 'thwack' ;
thwack . addEventListener ( 'request' , ( event ) => {
console . log ( 'hitting URL' , thwack . getUri ( event . options ) ) ;
} ) ;
Если вы используете React, вот хук, который вы можете «использовать» в своем приложении и который выполнит то же самое.
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 ;
Вот фрагмент кода о том, как его использовать.
const App = ( ) = {
useThwackLogger ( )
return (
< div >
...
</ div >
)
}
Допустим, у вас есть приложение, которое запросило некоторые пользовательские данные. Если приложение обращается к определенному URL-адресу (скажем, users
) и запрашивает определенный идентификатор пользователя (скажем, 123
), вы хотели бы предотвратить попадание запроса на сервер и вместо этого имитировать результаты.
status
в ThwackResponse
по умолчанию равен 200, поэтому, если вам не нужно имитировать неправильный ответ, вам нужно только вернуть 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
) ;
}
} ) ;
Часто желательно преобразовать DTO (объект передачи данных) во что-то более удобное для использования клиентом. В приведенном ниже примере мы преобразуем сложный DTO в firstName
, lastName
, avatar
и email
. Другие элементы данных, возвращаемые вызовом API, но не необходимые приложениям, игнорируются.
В этом примере приложения вы можете увидеть пример преобразования DTO, ведения журнала и возврата поддельных данных.
Вы можете просмотреть исходный код на CodeSandbox.
В этом примере у нас есть React Hook, который загружает изображение как URL-адрес Blob. Он кэширует URL-адрес и сопоставление URL-адресов Blob в хранилище сеансов. После загрузки любое обновление страницы мгновенно загружает изображение из URL-адреса 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 ;
} ;
См. этот пример на CodeSandbox.
Прямо сейчас у вас есть конечная точка REST по адресу https://api.example.com
. Предположим, вы опубликовали новую конечную точку REST на другом URL-адресе и хотели бы начать медленно перенаправлять 2% сетевого трафика на эти новые серверы.
Примечание. Обычно это обрабатывается вашим балансировщиком нагрузки на серверной стороне. Здесь показано только в демонстрационных целях.
Мы могли бы добиться этого, заменив options.url
в прослушивателе событий запроса следующим образом.
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`]
} ) ;
Наряду с use-thwack
, написание приложения для извлечения данных для React Native не может быть проще.
Просмотрите все приложение, работающее на Expo.
Твак во многом вдохновлен Axios. Спасибо, Мэтт!
Лицензия MIT
Спасибо этим замечательным людям (ключ смайлика):
Донавон Вест ? | Джереми Тайс | Юрайма Эстевес | Джереми Баргар | Брук Скарлетт Ялоф | Карл Горький | Кодзи |
Том Байрер | Ян Сазерленд | Блейк Йодер | Райан Хинчи | Миро Дойкич | Сантичевич |
Этот проект соответствует спецификации всех участников. Любой вклад приветствуется!