retrieve
TypeScript icon, indicating that this package has built-in type declarations

1.1.0 • Public • Published

retrieve

Tests passing

A convenience wrapper around fetch for the browser (well, anything that has fetch really).

This package’s files are distributed in the ES module format and have not been transpiled.

Links:

Features (see Features for more detailed descriptions):

Why is it called retrieve? I wanted to call it makeRequest (I like clean and explicit names), but that already exists on npm. So I went with retrieve because that's similar to fetch.

Contents

Installation & usage

As npm package

Install the retrieve package.

npm install retrieve

Import the retrieve function and use it.

import { retrieve } from 'retrieve'

const { data, response } = await retrieve({ url: 'http://example.org' })
console.dir(data, response)

As plain JS file

Download the retrieve module.

curl -O 'https://cdn.jsdelivr.net/npm/retrieve@latest/dist/retrieve.js'

Import the retrieve function and use it.

<script type='module'>
	import { retrieve } from './retrieve.js'

	const { data, response } = await retrieve({ url: 'http://example.org' })
	console.dir(data, response)
</script>

Documentation

Basic usage of retrieve looks like this:

const { data } = await retrieve({
	url: 'https://pokeapi.co/api/v2/pokemon',
})

Parameters

config

A RetrieveConfig object.

url

The request URL.

  • URL: Will be used as-is.
  • string:
    • Absolute URL string: Will be used as-is.
    • Relative URL path string: Will be turned into an absolute URL (using config.baseUrl if set; otherwise window.location.origin).
baseUrl (optional)

Default: window.location.origin

Base for request URL. Ignored if config.url is a URL object or an absolute URL string.

params (optional)

Request query parameters. Will be appended to the request URL. Parameters already existing on the request URL will be overridden. New parameters will be added.

FormData is intentionally not supported because it cannot be easily and reliably turned into an URLSearchParams object. If you can guarantee that your FormData object doesn't hold files, you can provide config.params using new URLSearchParams(formData).

init (optional)

Init object passed to fetch.

The following changes are made to the init object before it is passed to fetch (but without changing config.init):

  • Headers: If no “content-type” header is set, it is determined automatically where appropriate:
    • “application/octet-stream” if config.data is an ArrayBuffer of Blob object
    • “plain/text” if config.data is a string
    • “application/json” if config.data is set and the request method isn't GET or HEAD
  • Body: If config.data is set, it will be used for init.body. See config.data description for more information.
  • Signal: If config.timeout is set to a positive number, it will be used to create init.signal using AbortSignal.timeout(config.timeout).
data (optional)

Request body data.

If config.data is set:

  • … and the “content-type” header is “application/json”, init.body is set to the result of JSON.stringify(config.data)
  • … otherwise, init.body is set to config.data. It's your responsibility to make sure config.data can used on init.body (see fetch() global function: parameters).
requestErrorMessage (optional)

Default: 'Unknown request error'

Message for request errors.

If set, it overrides the underlying error's own message which will then be set on the request error's cause property.

responseErrorMessage (optional)

Default: $statusCode $statusText (e.g. '404 Not Found')

Message for response errors.

timeout (optional)

Default: 0 (no timeout)

Request timeout in milliseconds.

beforeRequestHandlers (optional)

Processed right before a request is sent (i.e. before calling fetch). Allows making changes to the parameters passed to fetch after they've been processed by retrieve.

Example:

const config = {
	url: 'https://api.example.org',
	beforeRequestHandlers: [
		(url, init) => {
			const url = import.meta.env.MODE === 'development'
				? new URL('http://localhost:1234/api')
				: url
			return [url, init]
		},
	],
}
requestErrorHandlers (optional)

Processed if sending the request failed (i.e. the promise returned by fetch was rejected). Allows implementing corrective measures.

Exceptions during the processing of a request error handler are not caught.

A request error handler can have one of two results:

  • maintaining the error state of the request (indicated by returning { status: 'maintained', value: error })
  • correcting the error state of the request (indicated by returning { status: 'corrected', value: response })

Returning a result object with the corrected status and a Response object allows retrieve to continue processing the request as if no error occurred in the first place. Then, no further error request handlers will be processed.

Example:

const config = {
	url: 'https://api.example.org',
	requestErrorHandlers: [
		async (requestError, url, init) => {
			// Do something to fix the error cause
			const response = await fetch(url, init)

			return { status: 'corrected', value: response }
		},
	],
}

Returning a result object with the maintained status and an Error object makes retrieve continue treating the request as having errored. Note also that all request error handlers will be processed as long as the previous handlers maintain the error state.

Example:

const config = {
	url: 'https://api.example.org',
	requestErrorHandlers: [
		(requestError, url, init) => {
			// Do something with requestError
			requestError.message = 'ERR: ' + requestError.message

			return { status: 'maintained', value: requestError }
		},
	],
}
responseSuccessHandlers (optional)

Processed if sending the request succeeded and a response with a status code 200–299 was returned (i.e. the promise returned by fetch is fulfilled and yields a Response object whose ok property is set to true).

Exceptions during the processing of a response success handler are not caught.

Example:

const config = {
	url: 'https://api.example.org',
	responseErrorHandlers: [
		async (retrieveResponse, url, init) => {
			// Do something with retrieveResponse
			return retrieveResponse
		},
	],
}
responseErrorHandlers (optional)

Processed if sending the request succeeded and a response with a status code >=300 was returned (i.e. the promise returned by fetch is fulfilled and yields a Response object whose ok property is set to false).

Exceptions during the processing of a response error handler are not caught.

A response error handler can have one of two results:

  • maintaining the error state of the response (indicated by returning { status: 'maintained', value: error })
  • correcting the error state of the response (indicated by returning { status: 'corrected', value: response })

Returning a result object with the corrected status and a Response object allows retrieve to continue processing the response as if no error occurred in the first place. Then, no further error response handlers will be processed.

Example:

const config = {
	url: 'https://api.example.org',
	responseErrorHandlers: [
		async (responseError, retrieveResponse, url, init) => {
			if (responseError.response.status === 401) {
				// Do something to fix the error cause (e.g. refresh the user's session)
				const response = await fetch(url, init)

				return { status: 'corrected', value: response }
			}

			return { status: 'maintained', value: responseError }
		},
	],
}

Returning a result object with the maintained status and an ResponseError object makes retrieve continue treating the response as having errored. Note also that all response error handlers will be processed as long as the previous handlers maintain the error state.

Example:

const config = {
	url: 'https://api.example.org',
	responseErrorHandlers: [
		async (responseError, retrieveResponse, url, init) => {
			// Do something with responseError
			responseError.message = 'ERR: ' + responseError.message

			return { status: 'maintained', value: responseError }
		},
	],
}

Return value

A Promise that resolves to a RetrieveResponse object.

A RetrieveResponse object

Exceptions

TypeError

A TypeError is thrown when fetch does (see fetch() global function: Exceptions).

ResponseError

A ResponseError is thrown for fetch responses with a status code >=300.

Examples

Example 1: make simple API request

async function example() {
	const { data, response } = await retrieve({
		url: 'https://pokeapi.co/api/v2/pokemon/pikachu/',
	})
	console.dir(data, response)
}

example()

Example 2: use response error

async function example() {
	try {
		await retrieve({
			url: 'https://pokeapi.co/api/v2/pokemon/grogu/',
		})
	} catch (error) {
		console.dir(error)
	}
}

example()

Example 3: retrying requests

async function example() {
	await retrieve({
		url: 'http://api.example.org/status',
		responseErrorHandlers: [
			async (responseError, retrieveResponse, url, init) => {
				if (responseError.response.status === 401) {
					// Do something to fix the error cause (e.g. refresh the user's session)
					const response = await fetch(url, init)

					return { status: 'corrected', value: response }
				}

				return { status: 'maintained', value: responseError }
			},
		]
	})
}

example()

Example 4: submitting form data (POST)

Warning: This is an educational example only. As it stands, a plain HTML form element without any JavaScript will handle such a use case just fine and do a better job of it. No need for retrieve.

<form method="post">
	<label>
		Name
		<input type="text" name="name" value="value">
	</label>

	<label>
		Age
		<input type="number" name="age" value="0">
	</label>

	<label>
		File
		<input type="file" name="file">
	</label>

	<button>Submit</button>
</form>
const form = document.querySelector('form')

form.addEventListener('submit', function (event) {
	event.preventDefault()

	const form = event.target

	retrieve({
		url: form.action,
		data: new FormData(form),
		init: {
			method: form.method,
		},
	})
})

Example 5: submitting form data (GET)

Warning: This is an educational example only. As it stands, a plain HTML form element without any JavaScript will handle such a use case just fine and do a better job of it. No need for retrieve.

<form>
	<label>
		Name
		<input type="text" name="name" value="value">
	</label>

	<label>
		Age
		<input type="number" name="age" value="0">
	</label>

	<button>Submit</button>
</form>
const form = document.querySelector('form')

form.addEventListener('submit', function (event) {
	event.preventDefault()

	const form = event.target

	retrieve({
		url: form.action,
		params: new URLSearchParams(new FormData(form)),
		init: {
			method: form.method,
		},
	})
})

Features

Request content type guessing

The content type for the request is guessed based on the request body format (if one isn't set already).

  • application/octet-stream if config.data is an ArrayBuffer of Blob object
  • plain/text if config.data is a string
  • application/json if config.data is set and the request method isn't GET or HEAD

Request body serialization

The request body is automatically serialized for JSON request bodies.

Response body deserialization

The response body is automatically deserialized for JSON, FormData, or text response bodies based on the response's content-type header.

Returning a rejecting promise for error responses

In case of receiving a response with a status code >=300 from the underlying fetch call, retrieve will return a rejecting promise (with a ResponseError). The behavior of fetch is to return a resolving promise (with a Response) instead.

Interceptors

Four types of interceptors are supported:

  • Before request: processed before a request is sent
  • Request error: processed if a network error is encountered
  • Response success: processed if a response with status 200-299 is returned
  • Response error: processed if a response with status >=300 is returned

Both error interceptors support error correcting logic triggered by returning a new Response object (e.g. the result of a new fetch call).

See Example: retrying requests

Versioning

This package uses semantic versioning.

Package Sidebar

Install

DownloadsWeekly Downloads

1

Version

1.1.0

License

MIT

Unpacked Size

31.9 kB

Total Files

5

Last publish

Collaborators

  • kleinfreund