ronag / undici-fetch

A WHATWG Fetch implementation based on @nodejs/undici

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

undici-fetch

⚠️ This module is currently in beta. It is not spec compliant yet. v1 will be released when this can be a drop-in replacement for other Node.js fetch implementations.

npm i undici-fetch@beta

Built on Undici

Table of Contents

Benchmarks

npm run benchmarks

On my personal machine:

MacBook Pro (15-inch, 2018)
Processor 2.9 GHz 6-Core Intel Core i9
Memory 32 GB 2400 MHz DDR4

Results:

{
  undici: { startTime: 72530288319510n, endTime: 72544207363031n },
  node: { startTime: 72530283341532n, endTime: 72575809241450n },
  minipass: { startTime: 72530290384674n, endTime: 72576867597178n }
}
Results for 10000 subsequent requests: 
undici-fetch | total time: 13919043521ns (13919.044ms)
node-fetch | total time: 45525899918ns (45525.900ms)
minipass-fetch | total time: 46577212504ns (46577.213ms)
---
undici-fetch <> node-fetch percent change: -69.426%
undici-fetch <> minipass-fetch percent change: -70.116%

API

The default export for this module is a fetch client instance fetch. It uses the default Undici.Pool() options.

In order to pass options to the underlying Pool instance, use the named export buildFetch method. The fetch instance returned by this method should be used throughout a project. Behind the scenes, undici-fetch reuses the pool instances for similar url origins. The pools are memoized in a Map that is initialized in the buildFetch closure.

Notice: You must call fetch.close() at the end of your project in order to safely close all of the Undici request pools. This step is will be removed before v1 release when an auto-close feature is added (nodejs/undici#508).

const fetch = require('undici-fetch')

async function run() {
	const res = await fetch('https://example.com')
	const json = await res.json()

	console.log(json)

	await fetch.close()
}

run()

All mentions of stream.Readable or Readable in this documentation is referring to the Node.js Stream API Readable Class

Keep in mind that many of these classes were designed to be directly integrated with Undici and thus may not offer the best stand-alone dev experience. They follow the fetch spec as close as possible and should not deviate from that spec API.

Default Method: fetch(resource, [init])

  • resource string | Request
  • init object (optional)
    • method string (optional) - Defaults to 'GET'
    • headers Headers | HeadersInit (optional)
    • body Readable | null (optional)

Returns: Promise<Response>

// import and initialize all-at-once
const fetch = require('undici-fetch')()

// Promise Chain
fetch('https://example.com')
	.then(res => res.json())
	.then(json => console.log(json))

// Async/Await
const res = await fetch('https://example.com')
const json = await res.json()

Method: buildFetch()

Returns: fetch

const { buildFetch } = require('undici-fetch')
const fetch = buildFetch({ /* Undici.Pool options */ })

Class: Headers

Represents a WHATWG Fetch Spec Headers Class

new Headers([init])

  • init Iterable<[string, string]> | Record<string, string> (optional) - Initial header list to be cloned into the new instance
new Headers()

new Headers([
	["undici", "fetch"]
])

const headers = new Headers({
	"undici": "fetch"
})

new Headers(headers)

Instance Methods

Headers.append(name, value)

  • name string
  • value string

Returns: void

Non-destructive operation for adding header entries. When called multiple times with the same name, the values will be collected in a list and returned together when retrieved using Headers.get.

const headers = new Headers()

headers.append('undici', 'fetch')
headers.get('undici') // -> 'fetch'

headers.append('foobar', 'fuzz')
headers.append('foobar', 'buzz')
headers.get('foobar') // -> 'fuzz, buzz'

Headers.delete(name)

  • name string

Returns: void

Removes a header entry. This operation is destructive and cannot be restored. Does not throw an error if the given name does not exist. Reminder that Headers.get will return null if the name does not exist.

const headers = new Headers()

headers.append('undici', 'fetch')

headers.get('undici') // -> 'fetch'

headers.delete('undici')

headers.get('undici') // -> null

Headers.get(name)

  • name string

Returns: string | null

Retrieves a header entry. If the entry name has multiple values, they are returned as a string joined by ',' characters. If the name does not exist, this method returns null.

const headers = new Headers()

headers.append('undici', 'fetch')
headers.get('undici') // -> 'fetch'

headers.append('foobar', 'fuzz')
headers.append('foobar', 'buzz')
headers.get('foobar') // -> 'fuzz, buzz'

headers.get('nodejs') // -> null

Headers.has(name)

  • name string

Returns boolean

Checks for the existence of a given entry name.

const headers = new Headers()

headers.append('undici', 'fetch')
headers.has('undici') // -> true

Headers.set(name, value)

  • name string
  • value string

Returns: void

Destructive operation that will override any existing values for the given entry name. For a non-destructive alternative see Headers.append.

const headers = new Headers()

headers.set('foobar', 'fuzz')
headers.get('foobar') // -> 'fuzz'

headers.set('foobar', 'buzz')
headers.get('foobar') // -> 'buzz'

Headers.values()

Returns: IteratableIterator<string>

Each iteration of the headers values() iterator yields a header value

const headers = new Headers()

headers.set('abc', '123')
headers.set('def', '456')
headers.set('ghi', '789')
headers.append('ghi', '012')

for (const value of headers.values()) {
	console.log(value)
}

// -> '123'
// -> '456'
// -> '789, 012'

Headers.keys()

Returns: IteratableIterator<string>

Each iteration of the headers keys() iterator yields a header name

const headers = new Headers()

headers.set('abc', '123')
headers.set('def', '456')
headers.set('ghi', '789')
headers.append('ghi', '012')

for (const name of headers.keys()) {
	console.log(name)
}

// -> 'abc'
// -> 'def'
// -> 'ghi'

Headers.forEach(callback, [thisArg])

  • callback (value: string, key: string, iterable: Headers) => void
  • thisArg any (optional)

Returns: void

A Headers class can be iterated using .forEach(callback, [thisArg])

Optionally a thisArg can be passed which will be assigned to the this context of callback

const headers = new Headers([['abc', '123']])

headers.forEach(function (value, key, headers) {
	console.log(key, value)
})
// -> 'abc', '123'

Headers[Symbol.iterator]

Returns: Iterator<[string, string]>

A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry name and the second value is the header value.

const headers = new Headers()

headers.set('abc', '123')
headers.set('def', '456')
headers.set('ghi', '789')
headers.append('ghi', '012')

for (const [name, value] of headers) {
	console.log(name, value)
}

// -> 'abc', '123'
// -> 'def', '456'
// -> 'ghi', '789, 012'

Headers.entries()

Returns: IteratableIterator<[string, string]>

Each iteration of the headers entries() iterator yields the same entry result as the default iterator

Class: Body

Represents a WHATWG Fetch Spec Body Mixin

new Body([input])

  • input Readable | null | undefined (optional) - Defaults to null

This class is the core for the Request and Response classes. Since this class is only ever going to recieve response data from Undici requests, it only supports Readable streams.

new Body()
new Body(undefined)
new Body(null)
new Body(Readable.from('undici-fetch', { objectMode: false }))

Instance Properties

Body.body

  • Readable | null

A property representing the payload of the Body instance

Body.bodyUsed

  • boolean

A property representing the consumption state of the Body instance. Do not confuse this property with the Node.js stream state of Body.body. This property is used by the other instance methods for indicating to other parts of the API if the body has been consumed or not.

Instance Methods

Body.arrayBuffer()

Returns: Promise<Buffer>

Returns the Body.body content as a Node.js Buffer instance.

const body = new Body(Readable.from('undici-fetch', { objectMode: false }))

const buf = await body.arrayBuffer()
console.log(buf instanceof Buffer) // -> true
console.log(buf.toString('utf8')) // -> 'undici-fetch'

Body.blob()

Returns: never

Currently, this implementation does not support returning content as a blob. Calling this method will throw an error. This may change in future API updates.

const body = new Body(new Readable())

try {
	await body.blob()
} catch (err) {
	console.log(err.message) // -> 'Body.blob() is not supported yet by undici-fetch'
}

Body.formData()

Returns: never

Currently, this implementation does not support returning content as a blob. Calling this method will throw an error. This may change in future API updates.

const body = new Body(new Readable())

try {
	await body.formData()
} catch (err) {
	console.log(err.message) // -> 'Body.formData() is not supported yet by undici-fetch'
}

Body.json()

Returns: Promise<any>

Returns the Body.body content as a JSON object.

const content = JSON.stringify({ undici: 'fetch' })
const body = new Body(Readable.from(content, { objectMode: false }))

const res = await body.json()

console.log(res) // -> { undici: 'fetch' }

Body.text()

Returns: Promise<string>

Returns the Body.body content as a UTF-8 string.

const body = new Body(Readable.from('undici-fetch', { objectMode: false }))

const res = await body.text()

console.log(res) // -> 'undici-fetch'

Class: Request

Extends: Body

Represents a WHATWG Fetch Spec Request Class

new Request(input, [init])

  • input Request | string
  • init object (optional)
    • method string (optional) - Defaults to 'GET'
    • headers Headers | HeadersInit (optional)
    • body Readable | null | undefined (optional)

Creates a new Request object. The resulting instance can be passed directly to the fetch method. The input string will be transformed into a Node.js URL instance.

const request = new Request('https://example.com', {
	method: 'POST',
	body: Readable.from('undici-fetch', { objectMode: false })
})

const res = await fetch(request)

Instance Properties:

Request.url

  • URL

A Node.js URL Class instance representing the destination of the Request instance

Request.method

  • string

A property representing the type of the Request instance. Will be normalized to uppercase format and validated as one of http.METHODS.

Request.headers

  • Headers

A Headers class instance representing the Headers instance for the request instance.

Instance Methods:

Request.clone()

Returns: Request

Will throw an Error if the current instance Responce.bodyUsed is true. Returns a new Request instance based on the existing instance.

const request = new Request('https://example.com')

const newRequest = request.clone()

console.log(newRequest.url) // => 'https://example.com'

Class: Response

Extends: Body

Represents a WHATWG Fetch Spec Response Class

new Response(body, [init])

  • body Readable | null | undefined
  • init object (optional)
    • status number (optional) - Defaults to 200
    • statusText string (optional) - Defaults to ''
    • headers Headers | HeadersInit (optional)

Creates a new Response object. This is the result resolved from a successful fetch() call. Remember that this class extends from Body so you can use methods such as .text() and .json().

const response = new Response(Readable.from('undici-fetch', { objectMode: false }))

if (response.ok) {
	const text = await response.text()

	console.log(text) // -> 'undici-fetch'
}

Instance Properties

Response.headers

  • Headers

A property representing a Headers instance for the response.

Response.ok

  • boolean

A property representing if the response is ok. A Response is considered ok if the status is between 200 and 299 inclusive.

Response.status

  • number

A property representing the status code of the response.

Response.statusText

  • string

A property representing the status of the response instance based on http.STATUS_CODES.

Response.type

  • string

Defaults to 'default'. A property representing the type of response instance. Currently only used to indicate an error response from Response.error().

Instance Methods

Response.clone()

Returns: Response

Will throw an Error if the current instance Responce.bodyUsed is true.

Static Methods

Response.error()

Returns: Response

Generates a Response instance with type set to 'error' and a body set to null.

const errorResponse = Response.error()

Response.redirect(url, status)

  • url string - The redirect location--will be assigned to a 'location' header
  • status number - Must be one of: 301, 302, 303, 307, or 308

Returns: Response

const redirectResponse = Response.redirect('https://example.com', 301)

TypeScript

Similar to Undici, this module ships with its own TypeScript definitions. Make sure to install @types/node as well.

Spec Omissions

Fetch is a browser API, but this library is written in Node.js. We try to be as spec compliant as possible; however, some aspects just cannot be recreated on the server. All Fetch WHATWG Spec omissions are detailed here. Each part should have a summary of the omission, plus links to relevant spec section and additional documentation.

Entries in this section have been considered for the implementation and explicitly ommitted. If you do not find an aspect of the Fetch API listed here that is also missing from the implementation, open an issue describing the feature request.

About

A WHATWG Fetch implementation based on @nodejs/undici

License:MIT License


Languages

Language:JavaScript 100.0%