Adjust error tracking for useCall/useCallMulti (#7368)

This commit is contained in:
Jaco 2022-04-13 18:22:57 +02:00 committed by GitHub
parent 16f47b3f30
commit 0ba53ad55e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 20 deletions

View File

@ -9,7 +9,7 @@ import type { MountedRef } from './useIsMountedRef';
import { useEffect, useRef, useState } from 'react';
import { isNull, isUndefined } from '@polkadot/util';
import { isFunction, isNull, isUndefined } from '@polkadot/util';
import { useIsMountedRef } from './useIsMountedRef';
@ -45,10 +45,12 @@ type CallFn = (...params: unknown[]) => Promise<VoidFn>;
export type TrackFn = PromiseResult<AnyFunction> | QueryFn;
export interface Tracker {
error: Error | null;
fn: TrackFn | undefined | null | false;
isActive: boolean;
serialized: string | null;
subscriber: TrackFnResult | null;
type: 'useCall' | 'useCallMulti';
}
interface TrackerRef {
@ -64,6 +66,10 @@ function isMapFn (fn: unknown): fn is QueryMapFn {
return !!(fn as QueryTrackFn).meta?.type?.isMap;
}
function isQuery (fn: unknown): fn is QueryableStorageEntry<'promise', []> {
return !!fn && !isUndefined((fn as QueryableStorageEntry<'promise', []>).creator);
}
// extract the serialized and mapped params, all ready for use in our call
function extractParams <T> (fn: unknown, params: unknown[], { paramMap = transformIdentity }: CallOptions<T> = {}): [string, CallParams | null] {
return [
@ -74,20 +80,28 @@ function extractParams <T> (fn: unknown, params: unknown[], { paramMap = transfo
];
}
export function handleError (error: Error, tracker: TrackerRef, fn?: unknown): void {
console.error(
tracker.current.error = new Error(`${tracker.current.type}(${
isQuery(fn)
? `${fn.creator.section}.${fn.creator.method}`
: '...'
}):: ${error.message}:: ${error.stack || '<unknown>'}`)
);
}
// unsubscribe and remove from the tracker
export function unsubscribe (tracker: TrackerRef): void {
tracker.current.isActive = false;
if (tracker.current.subscriber) {
tracker.current.subscriber.then((u) => (u as VoidFn)()).catch(console.error);
tracker.current.subscriber
.then((u) => isFunction(u) && (u as VoidFn)())
.catch((e) => handleError(e as Error, tracker));
tracker.current.subscriber = null;
}
}
function isQuery (fn: TrackFn): fn is QueryableStorageEntry<'promise', []> {
return !isUndefined((fn as QueryableStorageEntry<'promise', []>).creator);
}
// subscribe, trying to play nice with the browser threads
function subscribe <T> (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn | undefined, params: CallParams, setValue: (value: any) => void, { transform = transformIdentity, withParams, withParamsTransform }: CallOptions<T> = {}): void {
const validParams = params.filter((p) => !isUndefined(p));
@ -117,16 +131,10 @@ function subscribe <T> (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn
: transform(value)
);
} catch (error) {
let extra = '...';
if (isQuery(fn)) {
extra = `${fn.creator.section}.${fn.creator.method}`;
}
throw new Error(`useCall(${extra}):: ${(error as Error).message}:: ${(error as Error).stack || '<unknown>'}`);
handleError(error as Error, tracker, fn);
}
}
});
}).catch((error) => handleError(error as Error, tracker, fn));
} else {
tracker.current.subscriber = null;
}
@ -134,6 +142,16 @@ function subscribe <T> (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn
}, 0);
}
export function throwOnError (tracker: Tracker): void {
if (tracker.error) {
const error = tracker.error;
tracker.error = null;
throw error;
}
}
// tracks a stream, typically an api.* call (derive, rpc, query) that
// - returns a promise with an unsubscribe function
// - has a callback to set the value
@ -141,7 +159,7 @@ function subscribe <T> (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn
// FIXME This is generic, we cannot really use createNamedHook
export function useCall <T> (fn: TrackFn | undefined | null | false, params?: CallParams | null, options?: CallOptions<T>): T | undefined {
const mountedRef = useIsMountedRef();
const tracker = useRef<Tracker>({ fn: null, isActive: false, serialized: null, subscriber: null });
const tracker = useRef<Tracker>({ error: null, fn: null, isActive: false, serialized: null, subscriber: null, type: 'useCall' });
const [value, setValue] = useState<T | undefined>((options || {}).defaultValue);
// initial effect, we need an un-subscription
@ -164,5 +182,7 @@ export function useCall <T> (fn: TrackFn | undefined | null | false, params?: Ca
}
}, [fn, options, mountedRef, params]);
// throwOnError(tracker.current);
return value;
}

View File

@ -9,7 +9,7 @@ import type { MountedRef } from './useIsMountedRef';
import { useEffect, useRef, useState } from 'react';
import { useApi } from './useApi';
import { transformIdentity, unsubscribe } from './useCall';
import { handleError, transformIdentity, unsubscribe } from './useCall';
import { useIsMountedRef } from './useIsMountedRef';
interface TrackerRef {
@ -33,7 +33,6 @@ function subscribe <T> (api: ApiPromise, mountedRef: MountedRef, tracker: Tracke
if (filtered.length) {
// swap to active mode
tracker.current.isActive = true;
tracker.current.subscriber = api.queryMulti(filtered, (value): void => {
// we use the isActive flag here since .subscriber may not be set on immediate callback)
if (mountedRef.current && tracker.current.isActive) {
@ -51,11 +50,11 @@ function subscribe <T> (api: ApiPromise, mountedRef: MountedRef, tracker: Tracke
)
);
} catch (error) {
throw new Error(`useCallMulti(...):: ${(error as Error).message}:: ${(error as Error).stack || '<unknown>'}`);
handleError(error as Error, tracker);
}
}
}
});
}).catch((error) => handleError(error as Error, tracker));
} else {
tracker.current.subscriber = null;
}
@ -68,7 +67,7 @@ function subscribe <T> (api: ApiPromise, mountedRef: MountedRef, tracker: Tracke
export function useCallMulti <T> (calls?: QueryableStorageMultiArg<'promise'>[] | null | false, options?: CallOptions<T>): T {
const { api } = useApi();
const mountedRef = useIsMountedRef();
const tracker = useRef<Tracker>({ fn: null, isActive: false, serialized: null, subscriber: null });
const tracker = useRef<Tracker>({ error: null, fn: null, isActive: false, serialized: null, subscriber: null, type: 'useCallMulti' });
const [value, setValue] = useState<T>(() => (options || {}).defaultValue || [] as unknown as T);
// initial effect, we need an un-subscription
@ -90,5 +89,7 @@ export function useCallMulti <T> (calls?: QueryableStorageMultiArg<'promise'>[]
}
}, [api, calls, options, mountedRef]);
// throwOnError(tracker.current);
return value;
}