{"id":22599313,"url":"https://github.com/wjsoftware/dr-fetch","last_synced_at":"2025-09-17T21:08:29.014Z","repository":{"id":265757370,"uuid":"896585296","full_name":"WJSoftware/dr-fetch","owner":"WJSoftware","description":"Fetching done right, not just the happy path.","archived":false,"fork":false,"pushed_at":"2025-06-24T22:20:53.000Z","size":120,"stargazers_count":15,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-05T01:56:37.029Z","etag":null,"topics":["fetch","fetch-api","http-client","request"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WJSoftware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-11-30T18:53:50.000Z","updated_at":"2025-07-29T10:57:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"0cf54273-862a-4741-bf9f-7a1b97731959","html_url":"https://github.com/WJSoftware/dr-fetch","commit_stats":null,"previous_names":["wjsoftware/wj-fetch"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/WJSoftware/dr-fetch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fdr-fetch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fdr-fetch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fdr-fetch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fdr-fetch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WJSoftware","download_url":"https://codeload.github.com/WJSoftware/dr-fetch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fdr-fetch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275664898,"owners_count":25506157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fetch","fetch-api","http-client","request"],"created_at":"2024-12-08T11:08:57.803Z","updated_at":"2025-09-17T21:08:28.977Z","avatar_url":"https://github.com/WJSoftware.png","language":"TypeScript","readme":"# dr-fetch\n\nThis is not just one more wrapper for `fetch()`:  This package promotes the idea of using customized data-fetching \nfunctions, which is the most maintainable option, and adds features no other wrapper provides to date.\n\nThis package:\n\n+ Uses the modern, standardized `fetch` function.\n+ Does **not** throw on non-OK HTTP responses.\n+ **Can fully type all possible HTTP responses depending on the HTTP status code, even non-standard ones like 499.**\n+ **Supports abortable HTTP requests; no boilerplate.**\n+ **Can auto-abort HTTP requests in favor of newer request versions, with optional delaying (debouncing).**\n+ Works in any runtime that implements `fetch()` (browsers, NodeJS, etc.).\n+ Is probably the tiniest fetch wrapper you'll ever need:  **421 LOC** including typing (`npx cloc .\\src --exclude-dir=tests`).\n\n## Does a Non-OK Status Code Warrant an Error?\n\nThe short story is:\n\n1. Wrappers like `axios` or `ky` do `if (!response.ok) throw ...`, which forces code to `try..catch`.  This is a code \nsmell:  `try..catch` is being used as a branching mechanism.\n2. The performance drop is huge.  [See this benchmark](https://jsperf.app/dogeco).  Over 40% loss.\n\n[The issue of fetch wrappers explained in more detail](https://webjose.hashnode.dev/the-ugly-truth-all-popular-fetch-wrappers-do-it-wrong)\n\n## Quickstart\n\n1. Install the package.\n2. Create your custom fetch function, usually including logic to inject an authorization header/token.\n3. Create a fetcher object.\n4. Optionally add body processors.\n5. Use the fetcher for every HTTP request needed.\n\n### Installation\n\n```bash\nnpm i dr-fetch\n```\n\n### Create a Custom Fetch Function\n\nThis is optional and only needed if you need to do something before or after fetching.  By far the most common task to \ndo is to add the `authorization` header and the `accept` header to every call.\n\n```typescript\n// myFetch.ts\nimport { obtainToken } from \"./magical-auth-stuff.js\";\nimport { setHeaders, type FetchFnUrl, type FetchFnInit } from \"dr-fetch\";\n\nexport function myFetch(url: FetchFnUrl, init?: FetchFnInit) {\n    const token = obtainToken();\n    // Make sure there's an object where headers can be added:\n    init ??= {};\n    setHeaders(init, { Accept: 'application/json', Authorization: `Bearer ${token}`});\n    return fetch(url, init);\n}\n```\n\nThink of this custom function as the place where you do interceptions (if you are familiar with this term from `axios`).\n\n### Create Fetcher Object\n\n```typescript\n// fetcher.ts\nimport { DrFetch } from \"dr-fetch\";\nimport { myFetch } from \"./myFetch.js\";\n\nexport default new DrFetch(myFetch);\n// If you don't need a custom fetch function, just do:\nexport default new DrFetch();\n```\n\n### Adding a Custom Body Processor\n\nThis step is also optional.\n\nOne can say that the `DrFetch` class comes with 2 basic body processors:\n\n1. JSON processor when the value of the `content-type` response header is `application/json` or similar \n(`application/problem+json`, for instance).\n2. Text processor when the value of the `content-type` response header is `text/\u003csomething\u003e`, such as `text/plain` or \n`text/csv`.\n\nIf your API sends a content type not covered by any of the above two cases, use `DrFetch.withProcessor()` to add a \ncustom processor for the content type you are expecting.  The class allows for fluent syntax, so you can chain calls:\n\n```typescript\n// fetcher.ts\n...\n\nexport default new DrFetch(myFetch)\n    .withProcessor('desired/contentType', async (response, stockParsers) =\u003e {\n        // Do what you must with the provided response object.  Whatever you return is carried in the `body`\n        // property of the final DrFetch.fetch()'s response object.\n        return finalBody;\n    });\n    ;\n```\n\n\u003e [!NOTE]\n\u003e The matching pattern can be a string, a regular expression, or an array of either.  If this is not sufficient, pass \n\u003e a predicate function with signature `(response: Response, contentType: string) =\u003e boolean`.\n\nNow the fetcher object is ready for use.\n\n### Using the Fetcher Object\n\nThis is the fun part where we can enumerate the various shapes of the body depending on the HTTP status code:\n\n```typescript\nimport type { MyData } from \"./my-types.js\";\nimport fetcher from \"./fetcher.js\";\n\nconst response = await fetcher\n    .for\u003c200, MyData[]\u003e()\n    .for\u003c401, { loginUrl: string; }\u003e()\n    .fetch('/api/mydata/?active=true')\n    ;\n```\n\nThe object stored in the `response` variable will contain the following properties:\n\n+ `aborted`:  Will be `false` (since **v0.8.0**).\n+ `ok`:  Same as `Response.ok`.\n+ `status`:  Same as `Response.status`.\n+ `statusText`:  Same as `Response.statusText`.\n+ `headers`:  Same as `Response.headers` (since **v0.11.0**).\n+ `body`:  The HTTP response body, already parsed and typed according to the specification:  `MyData[]` if the status \ncode was `200`, or `{ loginUrl: string; }` if the status code was `401`.\n\nYour editor's Intellisense should be able to properly and accurately tell you all this:\n\n```typescript\nimport { StatusCodes } from \"dr-fetch\"; // Enum available since v0.11.0.\n\n// In this example, doing response.ok in the IF narrows the type just as well.\nif (response.status === StatusCodes.Ok) {\n    // Say, display the data somehow/somewhere.  In Svelte, we would set a store, perhaps?\n    myDataStore.set(response.body);\n}\nelse {\n    // TypeScript/Intellisense will tell you that the only other option is for the status code to be 401:\n    window.location.href = response.body.loginUrl;\n}\n```\n\n## The StatusCodes Enumeration\n\n\u003e Since v0.11.0\n\nAll standardized HTTP status codes documented at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status) \nfor the 2xx, 4xx and 5xx ranges have been collected into the `StatusCodes` enumeration.  Feel free to import it and use \nit to make your code far more readable and free of magic numbers.\n\n```typescript\nimport { StatusCodes } from \"dr-fetch\";\n\n...\nif (response.status === StatusCodes.Created) {\n    // Get resource URL from headers.\n}\nelse if (response.status === StatusCodes.BadRequest) {\n    ...\n}\n...\n```\n\n## Typing For Non-Standard Status Codes\n\n\u003e Since **v0.8.0**\n\nThis library currently supports, out of the box, the OK status codes, client error status codes and server error status \ncodes that the MDN website lists, and are therefore considered standardized.\n\nIf you need to type a response based on any other status code not currently supported, just do something like this:\n\n```typescript\nimport { DrFetch, type StatusCode } from \"dr-fetch\";\n\ntype MyStatusCode = StatusCode | 499;\nexport default new DrFetch\u003cMyStatusCode\u003e();\n```\n\nYou will now be able to use non-standardized status code `499` to type the response body with `DrFetch.for\u003c\u003e()`.\n\n## Abortable HTTP Requests\n\n\u003e Since **v0.8.0**\n\nTo create abortable HTTP requests, as per the standard, use an `AbortController`.  The following is how you would have \nto write your code *without* `dr-fetch`:\n\n```typescript\nconst ac = new AbortController();\nlet aborted = false;\nlet response: Response;\n\ntry {\n    response = await fetch('/url', { signal: ac.signal });\n}\ncatch (err) {\n    if (err instanceof DOMException \u0026\u0026 err.name === 'AbortError') {\n        aborted = true;\n    }\n    // Other stuff for non-aborted scenarios.\n}\nif (!aborted) {\n    const body = await response.json();\n    ...\n}\n```\n\nIn contrast, using an abortable fetcher from `dr-fetch`, you reduce your code to:\n\n```typescript\n// abortable-fetcher.ts\nimport { DrFetch } from \"dr-fetch\";\n\nexport const abortableFetcher = new DrFetch()\n    .abortable();\n```\n\n```typescript\n// some-component.ts\nimport { abortableFetcher } from \"./abortable-fetcher.js\";\n\nconst ac = new AbortController();\n\nconst response = await abortableFetcher\n    .for\u003c200, MyData[]\u003e(),\n    .for\u003c400, ValidationError[]\u003e()\n    .get('/url', { signal: ac.signal });\nif (!response.aborted) {\n    ...\n}\n```\n\nIn short:  All boilerplate is gone.  Your only job is to create the abort controller, pass the signal and after \nawaiting for the response, you check the value of the `aborted` property.\n\nTypeScript and Intellisense will be fully accurate:  If `response.aborted` is true, then the `response.error` property \nis available; otherwise the usual `ok`, `status`, `statusText` and `body` properties will be the ones available.\n\nFor full details and feedback on this feature, see [this discussion](https://github.com/WJSoftware/dr-fetch/discussions/25).\n\n\u003e [!IMPORTANT]\n\u003e Calling `DrFetch.abortable()` permanently changes the fetcher object's configuration.\n\n## Smarter Uses\n\nIt is smart to create just one fetcher, configure it, then use it for every fetch call.  Because generally speaking, \ndifferent URL's will carry a different body type, the fetcher object should be kept free of `for\u003c\u003e()` calls.  But what \nif your API is standardized so all status `400` bodies look the same?  Then configure that type:\n\n```typescript\n// root-fetcher.ts\nimport { DrFetch } from \"dr-fetch\";\nimport { myFetch } from \"./my-fetch.js\";\nimport type { BadRequestBody } from \"my-types.js\";\n\nexport default new DrFetch(myFetch)\n    .withProcessor(...) // Optional processors\n    .withProcessor(...)\n    .for\u003c400, BadRequestBody\u003e()\n    ;\n```\n\nYou can now consume this root fetcher object and it will be pre-typed for the `400` status code.\n\n### About Abortable Fetchers\n\n\u003e Since **v0.8.0**\n\nIf your project has a need for abortable and non-abortable fetcher objects, the smarter option would be to create and \nexport 2 fetcher objects, instead of one root fetcher:\n\n```typescript\n// root-fetchers.ts\nimport { DrFetch } from \"dr-fetch\";\nimport { myFetch } from \"./my-fetch.js\";\nimport type { BadRequestBody } from \"my-types.js\";\n\nexport const rootFetcher new DrFetch(myFetch)\n    .withProcessor(...) // Optional processors\n    .withProcessor(...)\n    .for\u003c400, BadRequestBody\u003e()\n    ;\n\nexport const abortableRootFetcher = rootFetcher.clone().abortable();\n```\n\nWe clone it because `abortable()` has permanent side effects on the object's state.  Cloning can also help with other \nscenarios, as explained next.\n\n### Specializing the Root Fetcher\n\nWhat if we needed a custom processor for just one particular URL?  It makes no sense to add it to the root fetcher, and \nmaybe it is even harmful to do so.  In cases like this one, clone the fetcher.\n\nCloning a fetcher produces a new fetcher with the same data-fetching function, the same body processors, the same \nsupport for abortable HTTP requests and the same body typings, **unless** we specify we want something different, like \nnot cloning the body types, or specifying a new data-fetching function.\n\n```typescript\nimport rootFetcher from \"./root-fetcher.js\";\nimport type { FetchFnUrl, FetchFnInit } from \"dr-fetch\";\n\nfunction specialFetch(url: FetchFnUrl, init?: FetchFnInit) {\n    ...\n}\n\n// Same data-fetching function, body processors, abortable support and body typing.\nconst specialFetcher = rootFetcher.clone();\n// Same data-fetching function, abortable support and body processors; no body typing.\nconst specialFetcher = rootFetcher.clone({ preserveTyping: false });\n// Same everything; different data-fetching function.\nconst specialFetcher = rootFetcher.clone({ fetchFn: specialFetch });\n// Same everything; no custom body processors.\nconst specialFetcher = rootFetcher.clone({ includeProcessors: false });\n// Identical processors, abortable support and body typing; stock fetch().\nconst specialFetcher = rootFetcher.clone({ fetchFn: false });\n// Identical processors, body typing and fetch function; no abortable support (the default when constructing).\nconst specialFetcher = rootFetcher.clone({ preserveAbortable: false });\n```\n\n\u003e [!IMPORTANT]\n\u003e `preserveTyping` is a TypeScript trick and cannot be a variable of type `boolean`.  Its value doesn't matter in \n\u003e runtime because types are not a runtime thing, and TypeScript depends on knowing if the value is `true` or `false`.\n\u003e \n\u003e On the other hand, `preserveAbortable` (since **v0.9.0**) is a hybrid:  It uses the same TypeScript trick, but its \n\u003e value does matter in runtime because an abortable fetcher object has different inner state than a stock fetcher \n\u003e object.  In this sense, supporting a variable would be ideal, but there's just no way to properly reconcile the \n\u003e TypeScript side with a variable of type `boolean`.  Therefore, try to always use constant values.\n\n## Auto-Abortable HTTP Requests\n\n\u003e Since **v0.9.0**\n\nAn HTTP request can automatically abort whenever a new version of the HTTP request is executed.  This is useful in \ncases like server-sided autocomplete components, where an HTTP request is made every time a user stops typing in the \nsearch textbox.  As soon as a new HTTP request is made, the previous has no value.  With `dr-fetch`, this chore is \nfully automated.\n\nTo illustrate, this is how it would be done \"by hand\", as if auto-abortable wasn't a feature:\n\n```typescript\nimport { abortableRootFetcher } from './root-fetchers.js';\nimport type { SimpleItem } from './my-types.js';\n\nlet ac: AbortController;\n\nasync function fetchAutocompleteList(searchTerm: string) {\n    ac?.abort();\n    ac = new AbortController();\n    const response = await abortableRootFetcher\n        .for\u003c200, SimpleItem[]\u003e()\n        .get(`/my/data?s=${searchTerm}`, { signal: ac.signal });\n    if (!response.aborted) {\n        ...\n    }\n}\n```\n\nWhile this is not too bad, it can actually be like this:\n\n```typescript\nimport { abortableRootFetcher } from './root-fetchers.js';\nimport type { SimpleItem } from './my-types.js';\n\nasync function fetchAutocompleteList(searchTerm: string) {\n    const response = await abortableRootFetcher\n        .for\u003c200, SimpleItem[]\u003e()\n        .get(`/my/data?s=${searchTerm}`, { autoAbort: 'my-key' });\n    if (!response.aborted) {\n        ...\n    }\n}\n```\n\n\u003e [!NOTE]\n\u003e The key can be a string, a number or a unique symbol.  Keys are not shared between fetcher instances, and cloning \n\u003e does not clone any existing keys.\n\n`DrFetch` instances create and keep track of abort controllers by key.  All one must do is provide a key when starting \nthe HTTP request.  Furthermore, the abort controllers are disposed as soon as the HTTP request resolves or rejects.\n\n### Delaying an Auto-Abortable HTTP Request\n\nAborting the HTTP request (the call to `fetch()`) is usually not the only thing that front-end developers do in cases \nlike the autocomplete component.  Developers usually also debounce the action of executing the HTTP request for a short \nperiod of time (around 500 milliseconds).\n\nYou can do this very easily as well with `dr-fetch`.  There is no need to program the debouncing externally.\n\nThis is the previous example, with a delay specified:\n\n```typescript\nimport { abortableRootFetcher } from './root-fetchers.js';\nimport type { SimpleItem } from './my-types.js';\n\nasync function fetchAutocompleteList(searchTerm: string) {\n    const response = await abortableRootFetcher\n        .for\u003c200, SimpleItem[]\u003e()\n        .get(`/my/data?s=${searchTerm}`, { autoAbort: { key: 'my-key', delay: 500 }});\n    if (!response.aborted) {\n        ...\n    }\n}\n```\n\nBy using the object form of `autoAbort`, one can specify the desired delay, in milliseconds.\n\n## Shortcut Functions\n\n\u003e Since **v0.3.0**\n\n`DrFetch` objects now provide the shortcut functions `get`, `head`, `post`, `patch`, `put` and `delete`.  Except for \n`get` and `head`, all these accept a body parameter.  When this body is a POJO or an array, the body is stringified \nand, if no explicit `Content-Type` header is set, the `Content-Type` header is given the value `application/json`.  If \na body of any other type is given (that the `fetch()` function accepts, such as `FormData`), no headers are explicitly \nadded and therefore it is up to what `fetch()` (or the custom data-fetching function you provide) does in these cases.\n\n```typescript\nimport type { Todo } from \"./myTypes.js\";\n\nconst newTodo = { text: 'I am new.  Insert me!' };\nconst response = await fetcher\n    .for\u003c200, { success: true; entity: Todo; }\u003e()\n    .for\u003c400, { errors: string[]; }\u003e()\n    .post('/api/todos', newTodo);\n\nconst newTodos = [{ text: 'I am new.  Insert me!' }, { text: 'Me too!' }];\nconst response = await fetcher\n    .for\u003c200, { success: true; entities: Todo[]; }\u003e()\n    .for\u003c400, { errors: string[]; }\u003e()\n    .post('/api/todos', newTodos);\n```\n\nAs stated, your custom fetch can be used to further customize the request because these shortcut functions will, in the \nend, call it.\n\n### Parameters\n\n\u003e Since **v0.8.0**\n\nThe `get` and `head` shortcut functions' parameters are:\n\n`(url: URL | string, init?: RequestInit)`\n\nThe other shortcut functions' parameters are:\n\n`(url: URL | string, body?: BodyInit | null | Record\u003cstring, any\u003e, init?: RequestInit)`\n\nJust note that `init` won't accept the `method` or `body` properties (the above is a simplification).\n\n## setHeaders and makeIterableHeaders\n\n\u003e Since **v0.4.0**\n\nThese are two helper functions that assist you in writing custom data-fetching functions.\n\nIf you haven't realized, the `init` parameter in `fetch()` can have the headers specified in 3 different formats:\n\n+ As a `Headers` object (an instance of the `Headers` class)\n+ As a POJO object, where the property key is the header name, and the property value is the header value\n+ As an array of tuples of type `[string, string]`, where the first element is the header name, and the second one is \nits value\n\nTo further complicate this, the POJO object also accepts an array of strings as property values for headers that accept \nmultiple values.\n\nSo writing a formal custom fetch **without** `setHeaders()` looks like this:\n\n```typescript\nimport type { FetchFnUrl, FetchFnInit } from \"dr-fetch\";\n\nexport function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {\n    const acceptHdrKey = 'Accept';\n    const acceptHdrValue = 'application/json';\n    init ??= {};\n    init.headers ??= new Headers();\n    if (Array.isArray(init.headers)) {\n        // Tuples, so push a tuple per desired header:\n        init.headers.push([acceptHdrKey, acceptHdrValue]);\n    }\n    else if (init.headers instanceof Headers) {\n        init.headers.set(acceptHdrKey, acceptHdrValue);\n    }\n    else {\n        // POJO object, so add headers as properties of an object:\n        init.headers[acceptHdrKey] = acceptHdrValue;\n    }\n    return fetch(url, init);\n}\n```\n\nThis would also get more complex if you account for multi-value headers.  The bottom line is:  This is complex.\n\nNow the same thing, using `setHeaders()`:\n\n```typescript\nimport type { FetchFnUrl, FetchFnInit } from \"dr-fetch\";\n\nexport function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {\n    init ??= {};\n    setHeaders(init, [['Accept', 'application/json']]);\n    // OR:\n    setHeaders(init, new Map([['Accept', ['application/json', 'application/xml']]]));\n    // OR:\n    setHeaders(init, { 'Accept': ['application/json', 'application/xml'] });\n    // OR:\n    setHeaders(init, new Headers([['Accept', 'application/json']]));\n    return fetch(url, init);\n}\n```\n\u003e [!NOTE]\n\u003e With `setHeaders()`, you can add headers to 'init' with a map, an array of tuples, a `Headers` instance or a POJO \n\u003e object.\n\nThe difference is indeed pretty shocking:  One line of code and you are done.  Also note that adding arrays of values \ndoesn't increase the complexity of the code:  It's still one line.\n\n### makeIterableHeaders\n\nThis function is the magic trick that powers the `setHeaders` function, and is very handy for troubleshooting or unit \ntesting because it can take a collection of HTTP header specifications in the form of a map, a `Headers` object, a POJO \nobject or an array of tuples and return an iterator object that iterates through the definitions in the same way:  A \nlist of tuples.\n\n```typescript\nconst myHeaders1 = new Headers();\nmyHeaders1.set('Accept', 'application/json');\nmyHeaders1.set('Authorization', 'Bearer x');\n\nconst myHeaders2 = new Map();\nmyHeaders2.set('Accept', 'application/json');\nmyHeaders2.set('Authorization', 'Bearer x');\n\nconst myHeaders3 = {\n    'Accept': 'application/json',\n    'Authorization': 'Bearer x'\n};\n\nconst myHeaders4 = [\n    ['Accept', 'application/json'],\n    ['Authorization', 'Bearer x'],\n];\n\n// The output of all these is identical.\nconsole.log([...makeIterableHeaders(myHeaders1)]);\nconsole.log([...makeIterableHeaders(myHeaders2)]);\nconsole.log([...makeIterableHeaders(myHeaders3)]);\nconsole.log([...makeIterableHeaders(myHeaders4)]);\n```\n\nThis function is a **generator function**, so what returns is an iterator object.  The two most helpful ways of using \nit are in `for..of` statements and spreading:\n\n```typescript\nfor (let [key, value] of makeIterableHeaders(myHeaders)) { ... }\n\n// In unit-testing, perhaps:\nexpect([...makeIterableHeaders(myHeaders)].length).to.equal(2);\n```\n\n## hasHeader and getHeader\n\n\u003e Since **v0.8.0**\n\nThese are two helper functions that do exactly what the names imply:  `hasHeader` checks for the existence of a \nparticular HTTP header; `getHeader` obtains the value of a particular HTTP header.\n\nThese functions perform a sequential search with the help of `makeIterableHeaders`.\n\n\u003e [!NOTE]\n\u003e Try not to use `getHeader` to determine the existence of a header **without** having the following in mind:  The \n\u003e function returns `undefined` if the value is not found, but it could return `undefined` if the header is found *and* \n\u003e its value is `undefined`.\n\n## Usage Without TypeScript (JavaScript Projects)\n\nWhy are you a weird fellow/gal?  Anyway, prejudice aside, body typing will mean nothing to you, so forget about `for()` \nand anything else regarding types.  Do your custom data-fetching function, add your custom body processors and fetch \naway using `.fetch()`, `.get()`, `head()`, `.post()`, `.put()`, `.patch()` or `.delete()`.\n\n## Plug-ins?  Fancy Stuff?\n\nIndeed, we can have fancy stuff.  As demonstration, this section will show you how one can add download progress with \na simple class, the `fetch-api-progress` NPM package and a custom body processor.\n\n[Live demo in the Svelte REPL](https://svelte.dev/playground/ddeedfb44ab74727ac40df320c552b92)\n\n\u003e [!NOTE]\n\u003e If you wanted to, `fetch-api-progress` also supports upload progress.  This is achieved by calling \n\u003e `trackRequestProgress` to create a specialized `RequestInit` object.  See [the next subsection](#custom-fetch-options) \n\u003e for details.\n\n```ts\nimport { trackResponseProgress } from \"fetch-api-progress\";\n\nexport class DownloadProgress {\n    progress = $state(0);\n    response;\n\n    constructor(response: Response) {\n        this.response = response;\n        trackResponseProgress(response, (p) =\u003e {\n            this.progress = p.lengthComputable ? p.loaded / p.total : 0;\n        });\n    }\n}\n```\n\nThe above class is a simple Svelte v5 class that exposes a reactive `progress` property.  Feel free to create \nequivalent classes/wrappers for your favorite frameworks.\n\nThe `response` property can be used to access the original response object, to, for instance, get the actual data.\n\n### How To Use\n\nCreate a custom processor for the content type that will be received, for example, `video/mp4` for MP4 video files.\n\n```ts\n// downloader.ts\nimport { DownloadProgress } from \"./DownloadProgress.svelte.js\";\n\nexport default new DrFetch(/* custom fetch function here, if needed */)\n    .withProcessor('video/mp4', (r) =\u003e Promise.resolve(new DownloadProgress(r)))\n    ;\n```\n\nThe Svelte component would use this fetcher object.  The response from `fetcher.fetch()` (or `fetcher.get()`) will \ncarry the class instance in the `body` property.\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n    import { DownloadProgress } from \"./DownloadProgress.svelte.js\";\n    import downloader from \"./downloader.js\";\n\n    let download = $state\u003cDownloadProgress\u003e();\n\n    async function startDownload() {\n        download = (await downloader\n            .for\u003c200, DownloadProgress\u003e()\n            .get('https://example.com/my-video.mp4')\n        )\n        .body;\n    }\n\u003c/script\u003e\n\n\u003cbutton type=\"button\" onclick={startDownload}\u003e\n    Start Download\n\u003c/button\u003e\n\u003cprogress value={download?.progress ?? 0}\u003e\u003c/progress\u003e\n```\n\nWhen the button is clicked, the download is started.  The custom processor simply creates the new instance of the \n`DownloadProgress` class.  Svelte's reactivity system takes care of the rest, effectively bringing the progress element \nto life as the download progresses.\n\n### Custom Fetch Options\n\n\u003e Since **v0.10.0**\n\nIf your custom data-fetching function (your custom `fetch()`) has abilities that require extra custom options from the \ncaller, you're in luck:  You can, and TypeScript and Intellisense will fully have your back.\n\nTo exemplify, let's do **upload progress** with the `fetch-api-progress` NPM package.\n\nAs first and last step, create your custom data-fetching function and give it to `DrFetch`:\n\n```typescript\n// uploader.ts\nimport { DrFetch, type FetchFnUrl, type FetchFnInit } from \"dr-fetch\";\nimport { trackRequestProgress, type FetchProgressEvent } from \"fetch-api-progress\";\n\nexport type UploaderInit = FetchFnInit \u0026 {\n    onProgress?: (progress: FetchProgressEvent) =\u003e void;\n}\n\nfunction uploadingFetch(url: FetchFnUrl, init?: UploaderInit) {\n    const trackedRequest = trackRequestProgress(init, init.onProgress);\n    return fetch(url, trackedRequest);\n}\n\nexport default new DrFetch(uploadingFetch); // This will be fully typed to support onProgress.\n```\n\nSimple, right?  I hope it is.\n\n```typescript\nimport uploader from './uploader.js';\n\nconst response = await uploader\n    .for\u003c200, undefined\u003e()\n    .post(\n        '/upload/big/data',\n        bigDataBody,\n        {\n            onProgress: (p) =\u003e console.log('Progress: %s', (p.lengthComputable \u0026\u0026 (p.loaded / p.total).toFixed(2)) || 0)\n        }\n    );\n```\n\n### I want fancier!\n\nIf you feel you definitely need more, remember that `DrFetch` is a class.  You can always extend it as per JavaScript's \nown rules.\n\nAre you still stuck?  [Open a new issue](https://github.com/WJSoftware/dr-fetch/issues) if you have an idea for a \nfeature that cannot be easily achieved in \"user land\".\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjsoftware%2Fdr-fetch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwjsoftware%2Fdr-fetch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjsoftware%2Fdr-fetch/lists"}