Skip to content

Commit

Permalink
Add option for custom fetch implementation (#273)
Browse files Browse the repository at this point in the history
* Overwrite globals.fetch when a 'fetch' option was provided

* Fixed bug that wouldn't let me overwrite globals' properties

* Fixed bug where 'this' referenced to the wrong object

* Added basic documentation

* Overwriting globals.fetch affects all other ky calls, therefore switched to this._options.fetch

* Added basic tests

* Formatting

* More Formatting

* Update index.d.ts

* Update readme.md

* Use shorthand syntax for passing fetch function in examples

* Added more assertions to the test
and removed .serial()

* Added note that the user-defined fetch function has to be compatible with the standard

* Fixed test

* Plan for 12 assertions because every call for fetch is another assertion

* Remove trailing comma

* Remove configurable setting as it's not needed anymore

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
Co-authored-by: Seth Holladay <me@seth-holladay.com>
  • Loading branch information
3 people authored Jul 20, 2020
1 parent 6e02940 commit ccda3b9
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 6 deletions.
24 changes: 24 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,30 @@ export interface Options extends Omit<RequestInit, 'headers'> {
```
*/
onDownloadProgress?: (progress: DownloadProgress, chunk: Uint8Array) => void;

/**
User-defined `fetch` function.
Has to be fully compatible with the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) standard.
Use-cases:
1. Use custom `fetch` implementations like [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch).
2. Use the `fetch` wrapper function provided by some frameworks that use server-side rendering (SSR).
@default fetch
@example
```
import ky from 'ky';
import fetch from 'isomorphic-unfetch';
(async () => {
const parsed = await ky('https://example.com', {
fetch
}).json();
})();
```
*/
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
}

/**
Expand Down
13 changes: 7 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,18 @@ class TimeoutError extends Error {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

// `Promise.race()` workaround (#91)
const timeout = (request, ms, abortController) =>
const timeout = (request, abortController, options) =>
new Promise((resolve, reject) => {
const timeoutID = setTimeout(() => {
if (abortController) {
abortController.abort();
}

reject(new TimeoutError(request));
}, ms);
}, options.timeout);

/* eslint-disable promise/prefer-await-to-then */
globals.fetch(request)
options.fetch(request)
.then(resolve)
.catch(reject)
.then(() => {
Expand Down Expand Up @@ -239,7 +239,8 @@ class Ky {
prefixUrl: String(options.prefixUrl || ''),
retry: normalizeRetryOptions(options.retry),
throwHttpErrors: options.throwHttpErrors !== false,
timeout: typeof options.timeout === 'undefined' ? 10000 : options.timeout
timeout: typeof options.timeout === 'undefined' ? 10000 : options.timeout,
fetch: options.fetch || globals.fetch
};

if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globals.Request)) {
Expand Down Expand Up @@ -449,10 +450,10 @@ class Ky {
}

if (this._options.timeout === false) {
return globals.fetch(this.request.clone());
return this._options.fetch(this.request.clone());
}

return timeout(this.request.clone(), this._options.timeout, this.abortController);
return timeout(this.request.clone(), this.abortController, this._options);
}

/* istanbul ignore next */
Expand Down
23 changes: 23 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,29 @@ import bourne from '@hapijs/bourne';
})();
```

##### fetch

Type: `Function`\
Default: `fetch`

User-defined `fetch` function.
Has to be fully compatible with the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) standard.

Use-cases:
1. Use custom `fetch` implementations like [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch).
2. Use the `fetch` wrapper function provided by some frameworks that use server-side rendering (SSR).

```js
import ky from 'ky';
import fetch from 'isomorphic-unfetch';

(async () => {
const parsed = await ky('https://example.com', {
fetch
}).json();
})();
```

### ky.extend(defaultOptions)

Create a new `ky` instance with some defaults overridden with your own.
Expand Down
16 changes: 16 additions & 0 deletions test/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ test.serial('relative URLs are passed to fetch unresolved', async t => {
t.is(await ky('unicorn', {prefixUrl: '/api/'}).text(), '/api/unicorn');
global.fetch = originalFetch;
});

test('fetch option takes a custom fetch function', async t => {
t.plan(12);

const customFetch = async input => {
t.true(input instanceof Request);
return new Response(input.url);
};

t.is(await ky('/unicorn', {fetch: customFetch}).text(), '/unicorn');
t.is(await ky('/unicorn', {fetch: customFetch, searchParams: {foo: 'bar'}}).text(), '/unicorn?foo=bar');
t.is(await ky('/unicorn#hash', {fetch: customFetch, searchParams: 'foo'}).text(), '/unicorn?foo=#hash');
t.is(await ky('/unicorn?old', {fetch: customFetch, searchParams: 'new'}).text(), '/unicorn?new=');
t.is(await ky('/unicorn?old#hash', {fetch: customFetch, searchParams: 'new'}).text(), '/unicorn?new=#hash');
t.is(await ky('unicorn', {fetch: customFetch, prefixUrl: '/api/'}).text(), '/api/unicorn');
});

0 comments on commit ccda3b9

Please sign in to comment.