Skip to content

Commit c2478f7

Browse files
committed
async API
1 parent d0b91c6 commit c2478f7

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

src/api/apiMock.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const timeoutDurationInMs = 500;
2+
3+
const apiMockResponses = {};
4+
5+
apiMockResponses["/entities/2"] = {
6+
"/entities/2": {
7+
property1: "c",
8+
property2: "d",
9+
_links: {
10+
self: "/entities/2",
11+
children: ["/entities/1"],
12+
nonEmbeddedRelation: "/entities/3",
13+
},
14+
},
15+
};
16+
17+
apiMockResponses["/entities/1"] = {
18+
"/entities/1": {
19+
property1: "a",
20+
property2: "b",
21+
_links: {
22+
self: "/entities/1",
23+
parent: "/entities/2",
24+
},
25+
},
26+
27+
// embedded parent
28+
"/entities/2": apiMockResponses["/entities/2"]["/entities/2"],
29+
};
30+
31+
apiMockResponses["/entities/3"] = {
32+
"/entities/3": {
33+
property1: "1",
34+
property2: "2",
35+
_links: {
36+
self: "/entities/3",
37+
},
38+
},
39+
};
40+
41+
apiMockResponses["/entities"] = {
42+
"/entities": {
43+
count: 3,
44+
_links: {
45+
self: "/entities",
46+
items: ["/entities/1", "/entities/2", "/entities/3"],
47+
},
48+
},
49+
};
50+
51+
export function fetch(uri) {
52+
console.log(`fetch from network: ${uri}`);
53+
return new Promise((resolve, reject) => {
54+
setTimeout(() => {
55+
if (uri in apiMockResponses) {
56+
resolve(apiMockResponses[uri]);
57+
}
58+
59+
reject({ response: { status: 404 } });
60+
}, timeoutDurationInMs);
61+
});
62+
}

src/api/async.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { fetch } from "./apiMock.js";
2+
3+
const promiseCache = new Map();
4+
const resourceCache = new Map();
5+
6+
/**
7+
* public API
8+
*/
9+
export function getAsync(uri) {
10+
return fetchResourceAsync(uri);
11+
12+
// Returning resource does the job at the moment. Use proxies for more magic:
13+
/*
14+
return fetchResourceAsync(uri).then(
15+
(resource) => new ResourceProxy(resource)
16+
);*/
17+
}
18+
19+
export function getRelationAsync(uri, relation) {
20+
return fetchRelationAsync(uri, relation);
21+
22+
// Returning resource does the job at the moment. Use proxies for more magic:
23+
/*
24+
return fetchRelationAsync(uri, relation).then(
25+
(resource) => new ResourceProxy(resource)
26+
);*/
27+
}
28+
29+
export function clearCache() {
30+
promiseCache.clear();
31+
resourceCache.clear();
32+
}
33+
34+
/**
35+
* internal functions
36+
*/
37+
38+
function fetchResourceAsync(uri) {
39+
if (promiseCache.has(uri)) {
40+
return promiseCache.get(uri);
41+
}
42+
43+
let promise;
44+
if (resourceCache.has(uri)) {
45+
promise = Promise.resolve(resourceCache.get(uri));
46+
} else {
47+
promise = fetchFromNetwork(uri);
48+
}
49+
50+
promiseCache.set(uri, promise);
51+
return promise;
52+
}
53+
54+
async function fetchRelationAsync(uri, relation) {
55+
const resource = await fetchResourceAsync(uri);
56+
57+
const link = resource?._links[relation];
58+
59+
if (!Array.isArray(link)) {
60+
return fetchResourceAsync(resource?._links[relation]);
61+
}
62+
63+
// collection --> make sure all items are loaded
64+
let itemPromises = [];
65+
link.forEach((item) => itemPromises.push(fetchResourceAsync(item)));
66+
return (await Promise.allSettled(itemPromises)).map((result) => result.value);
67+
}
68+
69+
async function fetchFromNetwork(uri) {
70+
const apiResponse = await fetch(uri);
71+
72+
// populate resourceCache with all resources contained in api response
73+
for (const [key, value] of Object.entries(apiResponse)) {
74+
resourceCache.set(key, value);
75+
}
76+
77+
return resourceCache.get(uri);
78+
}
79+
80+
/*
81+
class ResourceProxy {
82+
constructor(originalObject) {
83+
const handler = {
84+
get: function (target, prop) {
85+
if (prop === Symbol.toPrimitive) {
86+
return () => "";
87+
}
88+
89+
if (
90+
[
91+
"then",
92+
"toJSON",
93+
Symbol.toStringTag,
94+
"state",
95+
"getters",
96+
"$options",
97+
"_isVue",
98+
"__file",
99+
"render",
100+
"constructor",
101+
].includes(prop)
102+
) {
103+
return undefined;
104+
}
105+
106+
// proxy to properties that actually exist on original object
107+
if (Reflect.has(target, prop)) {
108+
return Reflect.get(target, prop);
109+
}
110+
111+
// Normal property access: return a function that yields another LoadingStoreValue and renders as empty string
112+
return () => getRelationAsync(target?._links?.self, prop);
113+
},
114+
ownKeys(target) {
115+
return Reflect.ownKeys(target);
116+
},
117+
};
118+
return new Proxy(originalObject, handler);
119+
}
120+
} */

src/components/AsyncTest.vue

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<script setup>
2+
/*
3+
test async functionality
4+
*/
5+
6+
import { getAsync, getRelationAsync, clearCache } from "@/api/async.js";
7+
8+
// asyncTest1: fetching the same resource multiple times triggers single network request only
9+
const asyncTest1 = async () => {
10+
clearCache();
11+
12+
const entity1promise = getAsync("/entities/1");
13+
const entity1againPromise = getAsync("/entities/1");
14+
15+
// true --> returns the same promise
16+
console.log(entity1promise === entity1againPromise);
17+
18+
const entity1 = await entity1promise;
19+
const entity1again = await entity1againPromise;
20+
21+
console.log(entity1);
22+
console.log(JSON.parse(JSON.stringify(entity1)));
23+
console.log(entity1.property1);
24+
console.log(entity1.property2);
25+
26+
// true --> returns the same response
27+
console.log(entity1 === entity1again);
28+
29+
// true --> again the same response
30+
console.log(entity1 == (await getAsync("/entities/1")));
31+
};
32+
33+
// asyncTest2: embedded parent is loaded from cache (single network request only)
34+
const asyncTest2 = async () => {
35+
clearCache();
36+
37+
const entity1parent = await getRelationAsync("/entities/1", "parent");
38+
39+
// only 1 network request was made
40+
console.log(entity1parent);
41+
};
42+
43+
// asyncTest3: loading parent and loading entity directly results in same response
44+
const asyncTest3 = async () => {
45+
clearCache();
46+
47+
const entity1parentPromise = getRelationAsync("/entities/1", "parent");
48+
const entity2promise = getAsync("/entities/2");
49+
50+
// false --> not the same promise / 2 network requests are triggered
51+
console.log(entity1parentPromise === entity2promise);
52+
53+
// true --> in the end, returns the same response
54+
console.log((await entity1parentPromise) === (await entity2promise));
55+
};
56+
57+
// asyncTest4: loading same relation twice triggers only 1 network request
58+
const asyncTest4 = async () => {
59+
clearCache();
60+
61+
const promise1 = getRelationAsync("/entities/1", "parent");
62+
const promise2 = getRelationAsync("/entities/1", "parent");
63+
64+
// true --> returns the same response / only 1 network request is made
65+
console.log((await promise1) === (await promise2));
66+
};
67+
68+
// asyncTest5:
69+
const asyncTest5 = async () => {
70+
clearCache();
71+
72+
await getAsync("/entities/1");
73+
74+
getRelationAsync("/entities/2", "nonEmbeddedRelation");
75+
76+
getRelationAsync("/entities/1", "parent").then(() => {
77+
getRelationAsync("/entities/2", "nonEmbeddedRelation");
78+
});
79+
80+
// true --> returns the same response / only 1 network request is made
81+
// console.log((await promise1) === (await promise2));
82+
};
83+
84+
// asyncTest6:
85+
const asyncTest6 = async () => {
86+
clearCache();
87+
88+
const entity1 = await getAsync("/entities/1");
89+
90+
console.log(entity1);
91+
console.log(entity1.property1);
92+
console.log(entity1.property2);
93+
94+
// this needs proxies for the magic
95+
const promise = entity1.parent();
96+
console.log(promise);
97+
console.log(await promise);
98+
};
99+
100+
// asyncTest7:
101+
const asyncTest7 = async () => {
102+
clearCache();
103+
104+
const entities = await getAsync("/entities");
105+
console.log(entities);
106+
107+
const items = await getRelationAsync("/entities", "items");
108+
console.log(items);
109+
};
110+
111+
// asyncTest8:
112+
const asyncTest8 = async () => {
113+
clearCache();
114+
115+
const entity2 = await getAsync("/entities/2");
116+
console.log(entity2);
117+
118+
const children = await getRelationAsync("/entities/2", "children");
119+
console.log(children);
120+
};
121+
</script>
122+
123+
<template>
124+
<h1>Async tests</h1>
125+
<div><button @click="asyncTest1">asyncTest1</button></div>
126+
<div><button @click="asyncTest2">asyncTest2</button></div>
127+
<div><button @click="asyncTest3">asyncTest3</button></div>
128+
<div><button @click="asyncTest4">asyncTest4</button></div>
129+
<div><button @click="asyncTest5">asyncTest5</button></div>
130+
<div><button @click="asyncTest6">asyncTest6</button></div>
131+
<div><button @click="asyncTest7">asyncTest7</button></div>
132+
<div><button @click="asyncTest8">asyncTest8</button></div>
133+
</template>

0 commit comments

Comments
 (0)