Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.

Commit 89c6ad0

Browse files
committed
resolves #11
resolves #10 resolves #8
1 parent f0f7948 commit 89c6ad0

File tree

11 files changed

+2076
-89
lines changed

11 files changed

+2076
-89
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/node_modules
2-
/fetch-suspense.js
32
/fetch-suspense.d.ts
3+
/fetch-suspense.js

.mocharc.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
allowUncaught: false,
3+
checkLeaks: true,
4+
color: true,
5+
diff: true,
6+
extension: [ 'ts' ],
7+
forbidOnly: true,
8+
forbidPending: true,
9+
fullTrace: false,
10+
inlineDiff: true,
11+
recursive: true,
12+
require: [
13+
'@babel/register',
14+
'chai/register-expect',
15+
'jsdom-global/register',
16+
'ts-node/register',
17+
],
18+
sort: true,
19+
watch: false,
20+
watchExtensions: [ 'ts' ],
21+
};

.npmignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/node_modules
2+
/src
3+
/tests
24
/.gitignore
5+
/.mocharc.js
36
/.npmignore
47
/.travis.yml
58
/tsconfig.json
6-
/fetch-suspense.ts
9+
/yarn.lock

fetch-suspense.ts

Lines changed: 0 additions & 83 deletions
This file was deleted.

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fetch-suspense",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"author": "Charles Stover <[email protected]>",
55
"bugs": {
66
"url": "https://github.com/CharlesStover/fetch-suspense/issues"
@@ -14,14 +14,25 @@
1414
"build": "tsc",
1515
"dev": "tsc --watch",
1616
"prepublishOnly": "npm run build",
17-
"test": "exit 0"
17+
"test": "mocha tests/**/*.test.ts"
1818
},
1919
"dependencies": {
2020
"deep-equal": "^1.0.1"
2121
},
2222
"devDependencies": {
23+
"@babel/core": "^7.4.3",
24+
"@babel/register": "^7.4.0",
25+
"@types/chai": "^4.1.7",
2326
"@types/deep-equal": "^1.0.1",
27+
"@types/mocha": "^5.2.6",
2428
"@types/node": "^10.12.1",
29+
"@types/sinon": "^7.0.11",
30+
"chai": "^4.2.0",
31+
"jsdom": "^14.0.0",
32+
"jsdom-global": "^3.0.2",
33+
"mocha": "^6.1.3",
34+
"sinon": "^7.3.1",
35+
"ts-node": "^8.0.3",
2536
"typescript": "^3.1.5"
2637
}
2738
}

src/fetch-suspense.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import deepEqual = require('deep-equal');
2+
3+
4+
5+
type CreateUseFetch = (fetch: GlobalFetch['fetch']) => UseFetch;
6+
7+
interface Export extends UseFetch {
8+
createUseFetch: CreateUseFetch;
9+
default: UseFetch;
10+
}
11+
12+
interface FetchCache {
13+
fetch?: Promise<void>;
14+
error?: any;
15+
init: RequestInit | undefined;
16+
input: RequestInfo;
17+
response?: any;
18+
}
19+
20+
type UseFetch = (
21+
input: RequestInfo,
22+
init?: RequestInit | undefined,
23+
lifespan?: number,
24+
) => Object | string;
25+
26+
27+
28+
const createUseFetch: CreateUseFetch = (
29+
fetch: GlobalFetch['fetch'],
30+
): UseFetch => {
31+
32+
// Create a set of caches for this hook.
33+
const caches: FetchCache[] = [];
34+
35+
return (
36+
input: RequestInfo,
37+
init?: RequestInit | undefined,
38+
lifespan: number = 0,
39+
): Object | string => {
40+
41+
// Check each cache by this useFetch hook.
42+
for (const cache of caches) {
43+
44+
// If this cache matches the request,
45+
if (
46+
deepEqual(input, cache.input) &&
47+
deepEqual(init, cache.init)
48+
) {
49+
50+
// If an error occurred, throw it so that componentDidCatch can handle
51+
// it.
52+
if (Object.prototype.hasOwnProperty.call(cache, 'error')) {
53+
throw cache.error;
54+
}
55+
56+
// If a response was successful, return it.
57+
if (Object.prototype.hasOwnProperty.call(cache, 'response')) {
58+
return cache.response;
59+
}
60+
61+
// If we are still waiting, throw the Promise so that Suspense can
62+
// fallback.
63+
throw cache.fetch;
64+
}
65+
}
66+
67+
// If no request in the cache matched this one, create a new cache entry.
68+
const cache: FetchCache = {
69+
70+
// Make the fetch request.
71+
fetch: fetch(input, init)
72+
73+
// Parse the response.
74+
.then((response: Response): Promise<Object | string> => {
75+
const contentType: null | string =
76+
response.headers.get('Content-Type');
77+
if (
78+
contentType &&
79+
contentType.indexOf('application/json') !== -1
80+
) {
81+
return response.json();
82+
}
83+
return response.text();
84+
})
85+
86+
// Cache the response.
87+
.then((response: Object | string): void => {
88+
cache.response = response;
89+
})
90+
91+
// Handle an error.
92+
.catch((e: Error): void => {
93+
cache.error = e;
94+
})
95+
96+
// Invalidate the cache.
97+
.then((): void => {
98+
if (lifespan > 0) {
99+
setTimeout(
100+
(): void => {
101+
const index: number = caches.indexOf(cache);
102+
if (index !== -1) {
103+
caches.splice(index, 1);
104+
}
105+
},
106+
lifespan,
107+
);
108+
}
109+
}),
110+
init,
111+
input,
112+
};
113+
caches.push(cache);
114+
throw cache.fetch;
115+
};
116+
};
117+
118+
const _export: Export = Object.assign(
119+
createUseFetch(window.fetch), {
120+
createUseFetch,
121+
default: createUseFetch(window.fetch)
122+
});
123+
124+
export = _export;

tests/create-use-fetch.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { expect } from 'chai';
2+
import { createUseFetch } from '../fetch-suspense';
3+
import CommonJS = require('../fetch-suspense');
4+
5+
6+
7+
describe('createUseFetch', (): void => {
8+
9+
it('should be a function with 1 parameter via CommonJS', (): void => {
10+
expect(CommonJS.createUseFetch).to.be.a('function');
11+
expect(CommonJS.createUseFetch.length).to.equal(1);
12+
});
13+
14+
it('should be a function with 1 parameter via ES6', (): void => {
15+
expect(createUseFetch).to.be.a('function');
16+
expect(createUseFetch.length).to.equal(1);
17+
});
18+
});

tests/use-fetch.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { expect } from 'chai';
2+
import useFetch from '../fetch-suspense';
3+
import CommonJS = require('../fetch-suspense');
4+
5+
6+
7+
describe('useFetch', (): void => {
8+
9+
it('should be a function with 3 parameters via CommonJS', (): void => {
10+
expect(CommonJS).to.be.a('function');
11+
expect(CommonJS.length).to.equal(3);
12+
});
13+
14+
it('should be a function with 3 parameters via ES6', (): void => {
15+
expect(useFetch).to.be.a('function');
16+
expect(useFetch.length).to.equal(3);
17+
});
18+
});

tests/utils/constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const ERROR_REQUEST: RequestInfo = 'http://';
2+
3+
export const JSON_REQUEST: RequestInfo = 'https://json';
4+
5+
export const MOCK_ERROR: Error = new Error('Mock Error');
6+
7+
export const MOCK_JSON: Object = {
8+
mock: 'json',
9+
};
10+
11+
export const MOCK_STRING: string = 'mock string';
12+
13+
export const STRING_REQUEST: RequestInfo = 'https://string';
14+
15+
export const UNKNOWN_ERROR: Error =
16+
new Error('Test Error. The test should fail.');

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"declaration": true,
44
"downlevelIteration": true,
5-
"lib": [ "dom", "es5", "es2015.promise", "scripthost" ],
5+
"lib": [ "dom", "es5", "es2015.promise", "es2017", "scripthost" ],
66
"module": "commonjs",
77
"moduleResolution": "Node",
88
"noFallthroughCasesInSwitch": true,
@@ -18,5 +18,5 @@
1818
"strictPropertyInitialization": true,
1919
"target": "es5"
2020
},
21-
"files": [ "fetch-suspense.ts" ]
21+
"files": [ "src/fetch-suspense.ts" ]
2222
}

0 commit comments

Comments
 (0)