Ky是一个基于Fetch API的小巧而优雅的HTTP客户端
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,从URL导入Ky。例如,使用CDN:
import ky from 'https://esm.sh/ky';
API
ky(input, options?)
input
和options
与fetch
相同,还提供了额外的options
(见下文)。
返回一个带有Body
方法的Response
对象,以方便使用。因此,你可以直接调用ky.get(input).json()
,而不必先等待Response
。当这样调用时,将根据使用的body方法设置适当的Accept
头。与window.Fetch
的Body
方法不同,如果响应状态不在200...299
范围内,这些方法将抛出HTTPError
。此外,如果body为空或响应状态为204
,.json()
将返回空字符串,而不是因为空body而抛出解析错误。
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<User>('/api/users/2').json();
// user3是User类型
const user3 = await ky('/api/users/3').json<User>();
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相同。
当使用Request
实例作为input
时,任何改变URL的选项(如prefixUrl
)将被忽略。
options
类型:object
与fetch
options相同,外加以下额外选项:
method
类型:string
默认值:'get'
用于发起请求的HTTP方法。
内部会将标准方法(GET
、POST
、PUT
、PATCH
、HEAD
和DELETE
)转为大写,以避免由于大小写敏感性导致的服务器错误。
json
类型:object
和JSON.stringify()
接受的任何其他值
发送JSON的快捷方式。使用此选项代替body
选项。接受任何普通对象或值,这些值将被JSON.stringify()
处理并在正文中发送,同时设置正确的头部。
searchParams
类型:string | object<string, string | number | boolean> | Array<Array<string | number | boolean>> | URLSearchParams
默认值:''
要包含在请求URL中的搜索参数。设置此项将覆盖输入URL中的所有现有搜索参数。
接受URLSearchParams()
支持的任何值。
prefixUrl
类型:string | URL
在发起请求时,要预先添加到input
URL的前缀。它可以是任何有效的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
中有前导斜杠,以确保一致性并避免对input
URL 处理方式产生混淆。因为使用prefixUrl
时,input
不会遵循正常的 URL 解析规则,这改变了前导斜杠的含义。
retry
类型:object | number
默认值:
limit
:2
methods
:get
put
head
delete
options
trace
statusCodes
:408
413
429
500
502
503
504
afterStatusCodes
:413
、429
、503
maxRetryAfter
:undefined
backoffLimit
:undefined
delay
:attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000
一个对象,表示最大重试次数、允许的方法、允许的状态码、允许使用 Retry-After
时间的状态码,以及最大 Retry-After
时间的 limit
、methods
、statusCodes
、afterStatusCodes
和 maxRetryAfter
字段。
如果 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<string, Function[]>
默认值:{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[]
默认值:[]
此钩子使您能够读取和可选地修改响应。钩子函数接收规范化的请求、选项和响应的克隆作为参数。如果钩子函数的返回值是 Response
的实例,Ky 将使用它作为响应对象。
import ky from 'ky';
const response = await ky('https://example.com', {
hooks: {
afterResponse: [
(_request, _options, response) => {
// 你可以对响应做一些处理,比如记录日志
log(response);
// 或者返回一个新的 Response 实例来覆盖原响应
return new Response('一个不同的响应', {status: 200});
},
// 或者在遇到 403 错误时使用新的 token 重试
async (request, options, response) => {
if (response.status === 403) {
// 获取新的 token
const token = await ky('https://example.com/token').text();
// 使用新 token 重试
request.headers.set('Authorization', `token ${token}`);
return ky(request);
}
}
]
}
});
throwHttpErrors
类型: boolean
默认值: true
当响应状态码非 2xx 时(在跟随重定向之后),抛出 HTTPError
。如果要对重定向也抛出错误而不是跟随它们,请将 redirect
选项设置为 'manual'
。
将此选项设置为 false
可能在检查资源可用性并期望出现错误响应时有用。
注意:如果设为 false
,错误响应将被视为成功,请求不会重试。
onDownloadProgress
类型: Function
下载进度事件处理函数。
该函数接收 progress
和 chunk
参数:
progress
对象包含以下元素:percent
、transferredBytes
和totalBytes
。如果无法获取主体大小,totalBytes
将为0
。chunk
参数是Uint8Array
的实例。第一次调用时为空。
import ky from 'ky';
const response = await ky('https://example.com', {
onDownloadProgress: (progress, chunk) => {
// 示例输出:
// `0% - 0 of 1271 bytes`
// `100% - 1271 of 1271 bytes`
console.log(`${progress.percent * 100}% - ${progress.transferredBytes} of ${progress.totalBytes} bytes`);
}
});
parseJson
类型: Function
默认值: JSON.parse()
用户自定义 JSON 解析函数。
使用场景:
- 通过
bourne
包 解析 JSON 以防止原型污染。 - 使用
JSON.parse()
的reviver
选项 解析 JSON。
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 字符串化函数。
使用场景:
- 使用自定义
replacer
函数字符串化 JSON。
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
。 - 使用某些使用服务器端渲染(SSR)的框架提供的
fetch
包装函数。
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 作为 Headers
实例或普通对象传递。
你可以通过传递值为 undefined
的 header 来删除 header。
作为字符串传递 undefined
只有在 header 来自 Headers
实例时才会删除该 header。
类似地,你可以通过显式传递 undefined
来删除现有的 hooks
条目。
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
一个可以由 beforeRetry
钩子返回以停止重试的 Symbol
。这也会短路剩余的 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;
}
}
]
}
};
// 注意,如果返回 ky.stop,response 将为 `undefined`。
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()
创建实例时传递给 ky
的,或者是直接在执行请求时传递的)。
如果在发生 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);
}
}
常见问题
如何在Node.js中使用?
Node.js 18及更高版本原生支持fetch
,所以你可以直接使用这个包。
如何在使用服务器端渲染(SSR)的Web应用(React、Vue.js等)中使用?
与上述相同。
如何测试使用这个库的浏览器库?
要么使用可以在浏览器中运行的测试运行器,如Mocha,要么使用AVA和ky-universal
。了解更多。
如何在不使用Webpack等打包工具的情况下使用?
确保你的代码作为JavaScript模块(ESM)运行,例如在HTML文档中使用<script type="module">
标签。然后Ky可以直接被该模块导入,无需打包工具或其他工具。
<script type="module">
import ky from 'https://unpkg.com/ky/distribution/index.js';
const json = await ky('https://jsonplaceholder.typicode.com/todos/1').json();
console.log(json.title);
//=> 'delectus aut autem'
</script>
它与got
有何不同?
请参阅我的回答这里。Got由与Ky相同的人维护。
它与axios
有何不同?
请参阅我的回答这里。
它与r2
有何不同?
请参阅我在#10中的回答。
ky
是什么意思?
这只是我设法获得的一个随机短npm包名。不过,它在日语中确实有意义:
一种可发短信的俚语,KY是空気読めない(kuuki yomenai)的缩写,字面意思是"无法读懂空气"。这个短语用于形容一个人无法理解暗示的含义。
浏览器支持
最新版本的Chrome、Firefox和Safari。
Node.js支持
Node.js 18及更高版本。
相关
- fetch-extras - 用于处理Fetch的有用工具
- got - 简化的Node.js HTTP请求
- ky-hooks-change-case - 用于修改请求和响应对象大小写的Ky钩子