UNPKG

5.34 kBJavaScriptView Raw
1'use strict';
2
3const createArgErrorMessageProd = require('../private/createArgErrorMessageProd');
4const Cache = require('./Cache');
5const Loading = require('./Loading');
6const cacheEntrySet = require('./cacheEntrySet');
7
8/**
9 * Controls a loading [cache value]{@link CacheValue}.
10 * @kind class
11 * @name LoadingCacheValue
12 * @param {Loading} loading Loading to update.
13 * @param {Cache} cache Cache to update.
14 * @param {CacheKey} cacheKey Cache key.
15 * @param {Promise<CacheValue>} loadingResult Resolves the loading result (including any loading errors) to be set as the [cache value]{@link CacheValue} if loading isn’t aborted. Shouldn’t reject.
16 * @param {AbortController} abortController Aborts this loading and skips setting the loading result as the [cache value]{@link CacheValue}. Has no affect after loading ends.
17 * @fires Loading#event:start
18 * @fires Cache#event:set
19 * @fires Loading#event:end
20 * @example <caption>Ways to `import`.</caption>
21 * ```js
22 * import { LoadingCacheValue } from 'graphql-react';
23 * ```
24 *
25 * ```js
26 * import LoadingCacheValue from 'graphql-react/public/LoadingCacheValue.js';
27 * ```
28 * @example <caption>Ways to `require`.</caption>
29 * ```js
30 * const { LoadingCacheValue } = require('graphql-react');
31 * ```
32 *
33 * ```js
34 * const LoadingCacheValue = require('graphql-react/public/LoadingCacheValue');
35 * ```
36 */
37module.exports = class LoadingCacheValue {
38 constructor(loading, cache, cacheKey, loadingResult, abortController) {
39 if (!(loading instanceof Loading))
40 throw new TypeError(
41 typeof process === 'object' && process.env.NODE_ENV !== 'production'
42 ? 'Argument 1 `loading` must be a `Loading` instance.'
43 : createArgErrorMessageProd(1)
44 );
45
46 if (!(cache instanceof Cache))
47 throw new TypeError(
48 typeof process === 'object' && process.env.NODE_ENV !== 'production'
49 ? 'Argument 2 `cache` must be a `Cache` instance.'
50 : createArgErrorMessageProd(2)
51 );
52
53 if (typeof cacheKey !== 'string')
54 throw new TypeError(
55 typeof process === 'object' && process.env.NODE_ENV !== 'production'
56 ? 'Argument 3 `cacheKey` must be a string.'
57 : createArgErrorMessageProd(3)
58 );
59
60 if (!(loadingResult instanceof Promise))
61 throw new TypeError(
62 typeof process === 'object' && process.env.NODE_ENV !== 'production'
63 ? 'Argument 4 `loadingResult` must be a `Promise` instance.'
64 : createArgErrorMessageProd(4)
65 );
66
67 if (!(abortController instanceof AbortController))
68 throw new TypeError(
69 typeof process === 'object' && process.env.NODE_ENV !== 'production'
70 ? 'Argument 5 `abortController` must be an `AbortController` instance.'
71 : createArgErrorMessageProd(5)
72 );
73
74 /**
75 * When this loading started.
76 * @kind member
77 * @name LoadingCacheValue#timeStamp
78 * @type {HighResTimeStamp}
79 */
80 this.timeStamp = performance.now();
81
82 /**
83 * Aborts this loading and skips setting the loading result as the
84 * [cache value]{@link CacheValue}. Has no affect after loading ends.
85 * @kind member
86 * @name LoadingCacheValue#abortController
87 * @type {AbortController}
88 */
89 this.abortController = abortController;
90
91 if (!(cacheKey in loading.store)) loading.store[cacheKey] = new Set();
92
93 const loadingSet = loading.store[cacheKey];
94
95 // In this constructor the instance must be synchronously added to the cache
96 // key’s loading set, so instances are set in the order they’re constructed
97 // and the loading store is updated for sync code following construction of
98 // a new instance.
99
100 let loadingAddedResolve;
101
102 const loadingAdded = new Promise((resolve) => {
103 loadingAddedResolve = resolve;
104 });
105
106 /**
107 * Resolves the loading result, after the [cache value]{@link CacheValue}
108 * has been set if the loading wasn’t aborted. Shouldn’t reject.
109 * @kind member
110 * @name LoadingCacheValue#promise
111 * @type {Promise<*>}
112 */
113 this.promise = loadingResult.then(async (result) => {
114 await loadingAdded;
115
116 if (
117 // The loading wasn’t aborted.
118 !this.abortController.signal.aborted
119 ) {
120 // Before setting the cache value, await any earlier loading for the
121 // same cache key to to ensure events are emitted in order and that the
122 // last loading sets the final cache value.
123
124 let previousPromise;
125
126 for (const loadingCacheValue of loadingSet.values()) {
127 if (loadingCacheValue === this) {
128 // Harmless to await if it doesn’t exist.
129 await previousPromise;
130 break;
131 }
132
133 previousPromise = loadingCacheValue.promise;
134 }
135
136 cacheEntrySet(cache, cacheKey, result);
137 }
138
139 loadingSet.delete(this);
140
141 if (!loadingSet.size) delete loading.store[cacheKey];
142
143 loading.dispatchEvent(
144 new CustomEvent(`${cacheKey}/end`, {
145 detail: {
146 loadingCacheValue: this,
147 },
148 })
149 );
150
151 return result;
152 });
153
154 loadingSet.add(this);
155
156 loadingAddedResolve();
157
158 loading.dispatchEvent(
159 new CustomEvent(`${cacheKey}/start`, {
160 detail: {
161 loadingCacheValue: this,
162 },
163 })
164 );
165 }
166};