Fix use of abort controller and parallel lookup of url

This commit is contained in:
Samy Pessé
2023-12-12 20:58:37 +01:00
parent fff0ea8c34
commit f13e6cb55b
5 changed files with 40 additions and 72 deletions
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -14,7 +14,7 @@
},
"dependencies": {
"@geist-ui/icons": "^1.0.2",
"@gitbook/api": "^0.17.0",
"@gitbook/api": "^0.20.1",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-popover": "^1.0.7",
"@readme/openapi-parser": "^2.5.0",
-1
View File
@@ -56,7 +56,6 @@ export const getPublishedContentByUrl = cache(
// If the request is aborted, we don't need to make the API call
// We call it as this logic is wrapped in an asynchronous cache that is not tied to the signal.
console.log('getPublishedContentByUrl', signal?.aborted);
signal?.throwIfAborted();
const gitbook = new GitBookAPI({
+4 -29
View File
@@ -12,7 +12,6 @@ const redis =
: null;
const memoryCache = new Map<string, any>();
const pendingOps = new Set<Promise<any>>();
export interface CacheResult<Result> {
data: Result;
@@ -76,14 +75,6 @@ export function cacheResponse<Result>(
};
}
/**
* Wait for all cache operations to be completed.
* This is a workaround until https://github.com/upstash/upstash-redis/issues/778 is fixed.
*/
export async function waitForCache() {
await Promise.all(pendingOps);
}
/**
* Create a cache key from a function name and its arguments.
*/
@@ -100,9 +91,7 @@ async function getCacheValue(key: string) {
}
if (redis) {
console.log('getCacheValue', key);
const value = await wrapOperation(redis.get(key));
console.log('done getCacheValue', key);
const value = await redis.get(key);
return value;
}
@@ -116,22 +105,8 @@ async function setCacheValue(key: string, value: any, ttl: number) {
memoryCache.set(key, value);
if (redis) {
console.log('setCacheValue', key);
await wrapOperation(
redis.set(key, value, {
ex: ttl,
}),
);
console.log('done setCacheValue', key);
await redis.set(key, value, {
ex: ttl,
});
}
}
/**
* Wrap a cache operation, it can be later awaited with `waitForCache`.
*/
function wrapOperation<T>(op: Promise<T>): Promise<T> {
pendingOps.add(op);
return op.finally(() => {
pendingOps.delete(op);
});
}
+35 -41
View File
@@ -235,57 +235,51 @@ async function lookupSpaceByAPI(
} alternatives`,
);
const found = await new Promise<PublishedContentLookup>((resolve, reject) => {
let resolved = false;
console.time('lookupSpaceByAPI');
try {
const abort = new AbortController();
Promise.all(
const matches = await Promise.all(
lookupAlternatives.map(async (alternative) => {
console.log(`lookup content for url "${alternative.url}"`)
const data = await getPublishedContentByUrl(
alternative.url,
apiEndpoint,
visitorAuthToken,
{
signal: abort.signal,
},
);
try {
const data = await getPublishedContentByUrl(
alternative.url,
apiEndpoint,
visitorAuthToken,
{
signal: abort.signal,
},
);
if (resolved) {
return;
}
resolved = true;
console.log('aborting');
abort.abort();
if ('redirect' in data) {
if (alternative.url === url.toString()) {
return data;
}
if (alternative.url === url.toString()) {
resolve(data);
} else if (!('redirect' in data)) {
resolve({
return null;
}
// Cancel all other requests to speed up the lookup
abort.abort();
return {
space: data.space,
basePath: data.basePath,
pathname: joinPath(data.pathname, alternative.extraPath),
apiToken: data.apiToken,
});
} as PublishedContentLookup;
} catch (error) {
// @ts-ignore
if (error.name === 'AbortError') {
return null;
}
throw error;
}
}),
).catch((error) => {
if (error.name === 'AbortError') {
return;
}
if (resolved) {
return;
}
resolved = true;
reject(error);
});
});
await waitForCache();
return found;
);
return matches.find((match) => match !== null) ?? null;
} finally {
console.timeEnd('lookupSpaceByAPI');
}
}
function computeLookupAlternatives(url: URL) {