Ky — это миниатюрный и элегантный HTTP-клиент, основанный на Fetch API
Ky ориентирован на современные браузеры, Node.js, Bun и Deno.
Это всего лишь небольшой пакет без зависимостей.
Преимущества по сравнению с обычным fetch
- Более простой API
- Сокращения для методов (
ky.post()) - Рассматривает коды состояния, отличные от 2xx, как ошибки (после редиректов)
- Повторяет неудачные запросы
- Опция для работы с JSON
- Поддержка таймаутов
- Опция префикса URL
- Инстансы с пользовательскими настройками по умолчанию
- Хуки
- Преимущества TypeScript (например,
.json()поддерживает дженерики и по умолчанию возвращаетunknown, а неany)
Установка
npm install ky###### CDN
Использование
import ky from 'ky';const json = await ky.post('https://example.com', {json: {foo: true}}).json();
console.log(json);
//=> {data: '🦄'}
С обычным fetch это выглядело бы так:
class HTTPError extends Error {}const response = await fetch('https://example.com', {
method: 'POST',
body: JSON.stringify({foo: true}),
headers: {
'content-type': 'application/json'
}
});
if (!response.ok) {
throw new HTTPError(Fetch error: ${response.statusText});
}
const json = await response.json();
console.log(json);
//=> {data: '🦄'}
Если вы используете Deno, импортируйте Ky по URL. Например, используя CDN:
import ky from 'https://esm.sh/ky';API
ky(input, options?)
Параметры input и options такие же, как у fetch, но доступны дополнительные опции (см. ниже).
Возвращает объект Response с добавленными для удобства методами Body. Таким образом, вы можете, например, вызывать ky.get(input).json() напрямую, не дожидаясь сначала Response. При таком вызове будет автоматически установлен соответствующий заголовок Accept в зависимости от используемого метода тела. В отличие от методов Body в window.Fetch, эти методы выбросят исключение HTTPError, если статус ответа не в диапазоне 200...299. Также .json() вернет пустую строку, если тело пустое или статус ответа 204, вместо того чтобы выбрасывать ошибку разбора из-за пустого тела.
import ky from 'ky';const user = await ky('/api/user').json();
console.log(user);
⌨️ TypeScript: Принимает необязательный типовой параметр, который по умолчанию равен unknown и передается в возвращаемый тип метода .json().
import ky from 'ky';// user1 имеет тип unknown
const user1 = await ky('/api/users/1').json();
// user2 имеет тип User
const user2 = await ky('/api/users/2').json();
// user3 имеет тип User
const user3 = await ky('/api/users/3').json();
console.log([user1, user2, user3]);
ky.get(input, options?)
ky.post(input, options?)
ky.put(input, options?)
ky.patch(input, options?)
ky.head(input, options?)
ky.delete(input, options?)
Устанавливает options.method в имя метода и выполняет запрос.
⌨️ TypeScript: Принимает необязательный типовой параметр для работы с JSON-ответами (см. ky()).
#### input
Тип: string | URL | Request
То же, что и fetch input.
Если в качестве input используется экземпляр Request, любые опции, изменяющие URL (например, prefixUrl), будут проигнорированы.
#### options
Тип: object
То же, что и опции fetch, плюс следующие дополнительные опции:
##### method
Тип: string\
По умолчанию: 'get'
HTTP-метод, используемый для выполнения запроса.
Внутри стандартные методы (GET, POST, PUT, PATCH, HEAD и DELETE) преобразуются к верхнему регистру, чтобы избежать ошибок сервера из-за чувствительности к регистру.
##### json
Тип: object и любое другое значение, принимаемое JSON.stringify()
Удобный способ отправки JSON. Используйте это вместо опции body. Принимает любой обычный объект или значение, который будет сериализован через JSON.stringify() и отправлен в теле с правильным заголовком.
##### searchParams
Тип: string | object\
По умолчанию: ''
Параметры запроса, которые будут включены в URL запроса. Установка этого значения перезапишет все существующие параметры запроса в исходном URL.
Принимает любые значения, поддерживаемые URLSearchParams().
##### prefixUrl
Тип: string | URL
Префикс, который добавляется к URL input при выполнении запроса. Это может быть любой валидный URL, относительный или абсолютный. Заключительный слэш / опционален и будет добавлен автоматически при необходимости при объединении с input. Действует только если input — строка. Аргумент input не может начинаться со слэша / при использовании этой опции.
Полезно использовать с ky.extend() для создания специализированных экземпляров Ky.
import ky from 'ky';// На https://example.com
const response = await ky('unicorn', {prefixUrl: '/api'});
//=> 'https://example.com/api/unicorn'
const response2 = await ky('unicorn', {prefixUrl: 'https://cats.com'});
//=> 'https://cats.com/unicorn'
Примечания:
- После объединения
prefixUrlиinputрезультат разрешается относительно базового URL страницы (если он есть). - Начальные слэши в
inputзапрещены при использовании этой опции для обеспечения согласованности и предотвращения путаницы в том, как обрабатывается URLinput, учитывая, чтоinputне будет следовать обычным правилам разрешения URL, если используетсяprefixUrl, что изменяет смысл начального слэша.
Тип: object | number\
По умолчанию:
limit:2methods:getputheaddeleteoptionstracestatusCodes:408413429500502503504afterStatusCodes:413,429,503maxRetryAfter:undefinedbackoffLimit:undefineddelay:attemptCount => 0.3 (2 (attemptCount - 1)) 1000
limit, methods, statusCodes, afterStatusCodes и maxRetryAfter для максимального количества попыток повторения, разрешённых методов, разрешённых кодов состояния, кодов состояния, для которых разрешено использовать время из заголовка Retry-After, и максимального времени Retry-After.Если retry — это число, оно будет использовано как limit, а остальные значения по умолчанию сохранятся.
Если ответ содержит HTTP-статус, указанный в afterStatusCodes, Ky будет ждать до даты, таймаута или метки времени, указанных в заголовке Retry-After, прежде чем повторить запрос. Если Retry-After отсутствует, вместо него используется нестандартный заголовок RateLimit-Reset в качестве запасного варианта. Если предоставленный код состояния отсутствует в списке, заголовок Retry-After будет проигнорирован.
Если maxRetryAfter установлен в undefined, будет использовано значение options.timeout. Если значение из заголовка Retry-After больше, чем maxRetryAfter, будет использовано maxRetryAfter.
Параметр backoffLimit — это верхний предел задержки между попытками в миллисекундах.
Чтобы ограничить задержку, например, установите backoffLimit в 1000.
По умолчанию задержка вычисляется как 0.3 (2 (attemptCount - 1)) 1000. Задержка увеличивается экспоненциально.
Опция delay может быть использована для изменения способа расчёта задержки между попытками. Функция получает один параметр — номер попытки, начиная с 1.
Повторные попытки не выполняются после таймаута.
import ky from 'ky';const json = await ky('https://example.com', {
retry: {
limit: 10,
methods: ['get'],
statusCodes: [413],
backoffLimit: 3000
}
}).json();
##### timeout
Тип: number | false\
Значение по умолчанию: 10000
Таймаут в миллисекундах для получения ответа, включая все повторные попытки. Не может быть больше 2147483647.
Если установлено значение false, таймаута не будет.
##### hooks
Тип: object\
Значение по умолчанию: {beforeRequest: [], beforeRetry: [], afterResponse: []}
Хуки позволяют вносить изменения в процессе выполнения запроса. Функции-хуки могут быть асинхронными и выполняются последовательно.
###### hooks.beforeRequest
Тип: Function[]\
Значение по умолчанию: []
Этот хук позволяет вам изменить запрос непосредственно перед его отправкой. После этого Ky больше не будет вносить изменений в запрос. Функция-хук получает в качестве аргументов request и options. Например, здесь вы можете изменить request.headers.
Хук может вернуть объект Request для замены исходящего запроса или объект Response, чтобы полностью избежать выполнения HTTP-запроса. Это может быть использовано для имитации запроса, проверки внутреннего кэша и т. д. Важно: если вы возвращаете объект запроса или ответа из этого хука, все оставшиеся хуки beforeRequest будут пропущены, поэтому рекомендуется возвращать их только из последнего хука.
import ky from 'ky';const api = ky.extend({
hooks: {
beforeRequest: [
request => {
request.headers.set('X-Requested-With', 'ky');
}
]
}
});
const response = await api.get('https://example.com/api/users');
###### hooks.beforeRetry
Тип: Function[]\
Значение по умолчанию: []
Этот хук позволяет изменить запрос непосредственно перед повторной попыткой. После этого Ky больше не будет вносить изменений в запрос. Функция-хук получает объект с нормализованным запросом и опциями, экземпляр ошибки и количество попыток. Например, здесь вы можете изменить request.headers.
Если на запрос получен ответ, ошибка будет типа HTTPError, и объект Response будет доступен в error.response. Обратите внимание, что некоторые типы ошибок, например сетевые ошибки, по своей природе означают, что ответа не было получено. В этом случае ошибка не будет экземпляром HTTPError.
Вы можете предотвратить повторную попытку запроса в Ky, выбросив ошибку. Ky не будет её обрабатывать и ошибка будет передана инициатору запроса. Остальные хуки beforeRetry в этом случае вызваны не будут. Альтернативно, вы можете вернуть символ ky.stop, чтобы сделать то же самое, но без проброса ошибки (имеет некоторые ограничения, смотрите документацию по ky.stop).
import ky from 'ky';const response = await ky('https://example.com', {
hooks: {
beforeRetry: [
async ({request, options, error, retryCount}) => {
const token = await ky('https://example.com/refresh-token');
request.headers.set('Authorization', token ${token});
}
]
}
});
###### hooks.beforeError
Тип: Function[]\
Значение по умолчанию: []
Этот хук позволяет изменить объект HTTPError непосредственно перед тем, как он будет выброшен. Функция-хук получает в качестве аргумента объект HTTPError и должна вернуть экземпляр HTTPError.
import ky from 'ky';await ky('https://example.com', {
hooks: {
beforeError: [
error => {
const {response} = error;
if (response && response.body) {
error.name = 'GitHubError';
error.message = ${response.body.message} (${response.status});
}
return error;
}
]
}
});
###### hooks.afterResponse
Тип: Function[]\
Значение по умолчанию: []
Этот хук позволяет прочитать и, при необходимости, изменить ответ. Функция-хук получает нормализованный запрос, опции и клон ответа в качестве аргументов. Возвращаемое значение функции-хука будет использовано Ky как объект ответа, если оно является экземпляром Response.
import ky from 'ky';const response = await ky('https://example.com', {
hooks: {
afterResponse: [
(_request, _options, response) => {
// Здесь можно что-то сделать с ответом, например, залогировать его.
log(response);
// Или вернуть экземпляр Response, чтобы перезаписать ответ.
return new Response('A different response', {status: 200});
},
// Или выполнить повторную попытку с новым токеном при ошибке 403
async (request, options, response) => {
if (response.status === 403) {
// Получить новый токен
const token = await ky('https://example.com/token').text();
// Повторить попытку с токеном
request.headers.set('Authorization', token ${token});
return ky(request);
}
}
]
}
});
##### throwHttpErrors
Тип: boolean\
Значение по умолчанию: true
Выбрасывать HTTPError, если после всех перенаправлений ответ содержит статус-код, отличный от 2xx. Чтобы также выбрасывать ошибку для перенаправлений вместо следования за ними, установите параметр redirect в значение 'manual'.
Установка этого параметра в false может быть полезна, если вы проверяете доступность ресурса и ожидаете ошибочные ответы.
Примечание: Если установлено в false, ошибочные ответы считаются успешными и повторные попытки предприняты не будут.
##### onDownloadProgress
Тип: Function
Обработчик событий прогресса загрузки.
Функция получает следующие аргументы:
progress— объект со следующими свойствами:- -
percent— число от 0 до 1, отражающее процент выполнения. - -
transferredBytes— количество байт, переданных на данный момент. - -
totalBytes— общее количество байт для передачи. Это оценка и может быть 0, если общий размер нельзя определить. chunk— экземплярUint8Array, содержащий отправленные данные. Примечание: при первом вызове он пустой.
import ky from 'ky';const response = await ky('https://example.com', {
onDownloadProgress: (progress, chunk) => {
// Пример вывода:
// 0% - 0 из 1271 байт
// 100% - 1271 из 1271 байт
console.log(${progress.percent * 100}% - ${progress.transferredBytes} из ${progress.totalBytes} байт);
}
});
##### onUploadProgress
Тип: Function
Обработчик событий прогресса загрузки.
Функция получает следующие аргументы:
progress— объект со следующими свойствами:- -
percent— число от 0 до 1, представляющее процент выполнения. - -
transferredBytes— количество байт, переданных на данный момент. - -
totalBytes— общее количество байт, которые необходимо передать. Это оценка и может быть равно 0, если общий размер не может быть определён. chunk— экземплярUint8Array, содержащий отправленные данные. Примечание: для последнего вызова массив пустой.
import ky from 'ky';const response = await ky.post('https://example.com/upload', {
body: largeFile,
onUploadProgress: (progress, chunk) => {
// Пример вывода:
// 0% - 0 из 1271 байт
// 100% - 1271 из 1271 байт
console.log(${progress.percent * 100}% - ${progress.transferredBytes} из ${progress.totalBytes} байт);
}
});
##### parseJson
Тип: Function\
По умолчанию: JSON.parse()
Пользовательская функция для парсинга JSON.
Варианты использования:
- Разбор JSON с помощью пакета
bourneдля защиты от загрязнения прототипа. - Разбор JSON с помощью
reviver-опции дляJSON.parse().
import ky from 'ky';
import bourne from '@hapijs/bourne';const json = await ky('https://example.com', {
parseJson: text => bourne(text)
}).json();
##### stringifyJson
Тип: Function\
По умолчанию: JSON.stringify()
Пользовательская функция для преобразования в JSON.
Варианты использования:
- Преобразование в JSON с помощью пользовательской функции
replacer.
import ky from 'ky';
import {DateTime} from 'luxon';const json = await ky('https://example.com', {
stringifyJson: data => JSON.stringify(data, (key, value) => {
if (key.endsWith('_at')) {
return DateTime.fromISO(value).toSeconds();
}
return value;
})
}).json();
##### fetch
Тип: Function\
По умолчанию: fetch
Пользовательская функция fetch.
Должна быть полностью совместима со стандартом Fetch API.
Варианты использования:
- Использование пользовательских реализаций
fetch, таких какisomorphic-unfetch. - Использование обёртки над
fetch, предоставленной некоторыми фреймворками, использующими серверный рендеринг (SSR).
import ky from 'ky';
import fetch from 'isomorphic-unfetch';const json = await ky('https://example.com', {fetch}).json();
ky.extend(defaultOptions)
Создаёт новый экземпляр ky с некоторыми переопределёнными по умолчанию значениями.
В отличие от ky.create(), ky.extend() наследует значения по умолчанию от родителя.
Заголовки можно передавать как экземпляр Headers, так и обычный объект.
Заголовок можно удалить через .extend(), передав заголовок со значением undefined.
Передача undefined как строки удаляет заголовок только в случае, если он был из экземпляра Headers.
Аналогично, можно удалить существующие записи hooks, расширив хук явно заданным значением undefined.
import ky from 'ky';const url = 'https://sindresorhus.com';
const original = ky.create({
headers: {
rainbow: 'rainbow',
unicorn: 'unicorn'
},
hooks: {
beforeRequest: [ () => console.log('before 1') ],
afterResponse: [ () => console.log('after 1') ],
},
});
const extended = original.extend({
headers: {
rainbow: undefined
},
hooks: {
beforeRequest: undefined,
afterResponse: [ () => console.log('after 2') ],
}
});
const response = await extended(url).json();
//=> after 1
//=> after 2
console.log('rainbow' in response);
//=> false
console.log('unicorn' in response);
//=> true
Также можно обратиться к значениям по умолчанию родителя, передав функцию в .extend().
import ky from 'ky';const api = ky.create({prefixUrl: 'https://example.com/api'});
const usersApi = api.extend((options) => ({prefixUrl: ${options.prefixUrl}/users}));
const response = await usersApi.get('123');
//=> 'https://example.com/api/users/123'
const response = await api.get('version');
//=> 'https://example.com/api/version'
ky.create(defaultOptions)
Создаёт новый экземпляр Ky с полностью новыми значениями по умолчанию.
import ky from 'ky';// На https://my-site.com
const api = ky.create({prefixUrl: 'https://example.com/api'});
const response = await api.get('users/123');
//=> 'https://example.com/api/users/123'
const response = await api.get('/status', {prefixUrl: ''});
//=> 'https://my-site.com/status'
#### defaultOptions
Тип: object
ky.stop
Symbol, который может быть возвращён хуком beforeRetry для остановки повторной попытки. Это также прерывает выполнение оставшихся хуков beforeRetry.
Примечание: Возврат этого символа заставляет Ky прервать выполнение и вернуть undefined в качестве ответа. Обязательно проверьте наличие ответа перед обращением к его свойствам или используйте опциональную цепочку. Также это несовместимо с методами для работы с телом ответа, такими как .json() или .text(), потому что ответа для разбора нет. В целом рекомендуется выбрасывать ошибку вместо возврата этого символа, так как это заставит Ky прервать выполнение и выбросить исключение, что позволит избежать этих ограничений.
Корректный вариант использования ky.stop — предотвращать повторные попытки при выполнении запросов с побочными эффектами, когда возвращаемые данные не важны. Например, при логировании активности клиента на сервере.
import ky from 'ky';const options = {
hooks: {
beforeRetry: [
async ({request, options, error, retryCount}) => {
const shouldStopRetry = await ky('https://example.com/api');
if (shouldStopRetry) {
return ky.stop;
}
}
]
}
};
// Обратите внимание, что response будет undefined, если возвращается ky.stop.
const response = await ky.post('https://example.com', options);// Использование .text() или других методов работы с телом не поддерживается.
const text = await ky('https://example.com', options).text();
HTTPError
Открыт для проверок с помощью instanceof. Ошибка содержит свойство response с объектом Response, свойство request с объектом Request и свойство options с нормализованными опциями (переданными либо при создании экземпляра через ky.create(), либо напрямую при выполнении запроса).
Имейте в виду, что некоторые типы ошибок, такие как сетевые ошибки, по своей сути означают, что ответ не был получен. В этом случае ошибка не будет экземпляром HTTPError и не будет содержать свойство response.
Если вам нужно прочитать фактический ответ при возникновении HTTPError, вызовите соответствующий метод парсера на объекте ответа. Например:
try {
await ky('https://example.com').json();
} catch (error) {
if (error.name === 'HTTPError') {
const errorJson = await error.response.json();
}
}⌨️ TypeScript: Принимает необязательный типовой параметр, который по умолчанию равен unknown, и передается в возвращаемый тип метода error.response.json().
TimeoutError
Ошибка, выбрасываемая при истечении времени ожидания запроса. Содержит свойство request с объектом Request.
Советы
Отправка данных формы
Отправка данных формы в Ky идентична fetch. Просто передайте экземпляр FormData в опцию body. Заголовок Content-Type будет автоматически установлен в multipart/form-data.
import ky from 'ky';// multipart/form-data
const formData = new FormData();
formData.append('food', 'fries');
formData.append('drink', 'icetea');
const response = await ky.post(url, {body: formData});
Если вы хотите отправить данные в формате application/x-www-form-urlencoded, вам нужно закодировать данные с помощью URLSearchParams.
import ky from 'ky';// application/x-www-form-urlencoded
const searchParams = new URLSearchParams();
searchParams.set('food', 'fries');
searchParams.set('drink', 'icetea');
const response = await ky.post(url, {body: searchParams});
Установка собственного Content-Type
Ky автоматически устанавливает соответствующий заголовок Content-Type для каждого запроса на основе данных в теле запроса. Однако некоторые API требуют нестандартных типов содержимого, например, application/x-amz-json-1.1. Используя опцию headers, вы можете вручную переопределить тип содержимого.
import ky from 'ky';const json = await ky.post('https://example.com', {
headers: {
'content-type': 'application/json'
},
json: {
foo: true
},
}).json();
console.log(json);
//=> {data: '🦄'}
Отмена запроса
Fetch (и, соответственно, Ky) поддерживает отмену запроса через AbortController API. Подробнее.
Пример:
import ky from 'ky';const controller = new AbortController();
const {signal} = controller;
setTimeout(() => {
controller.abort();
}, 5000);
try {
console.log(await ky(url, {signal}).text());
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
}
FAQ
#### Как использовать это в Node.js?
Node.js 18 и выше поддерживает fetch нативно, поэтому вы можете использовать этот пакет напрямую.
#### Как использовать это с веб-приложением (React, Vue.js и т. д.) с серверным рендерингом (SSR)?
Аналогично вышеуказанному.
#### Как тестировать библиотеку для браузера, использующую это?
Используйте тестовый раннер, который может запускаться в браузере, например Mocha, или используйте AVA с ky-universal. Подробнее.
#### Как использовать это без сборщика, такого как Webpack?
Убедитесь, что ваш код выполняется как модуль JavaScript (ESM), например, с помощью тега в вашем HTML-документе. Затем Ky можно импортировать непосредственно этим модулем без сборщика или других инструментов.
#### Чем отличается от got
Смотрите мой ответ здесь. Got поддерживается теми же людьми, что и Ky.
#### Чем отличается от axios?
Смотрите мой ответ здесь.
#### Чем отличается от r2?
Смотрите мой ответ в #10.
#### Что означает ky?
Это просто случайное короткое имя npm-пакета, которое мне удалось получить. Однако оно имеет значение на японском языке:
Форма сленга для сообщений, KY — это сокращение от 空気読めない (kuuki yomenai), что буквально переводится как «не может читать воздух». Этой фразой называют человека, который не улавливает скрытый смысл.
Поддержка браузеров
Последние версии Chrome, Firefox и Safari.
Поддержка Node.js
Node.js 18 и выше.
Связанные проекты
- fetch-extras - Полезные утилиты для работы с Fetch
- ky-hooks-change-case - Хуки Ky для изменения регистра в запросах и ответах объектов