Skip to content

Instantly share code, notes, and snippets.

@airhorns
Last active July 8, 2020 19:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airhorns/36598799d44217365b31ad9c840b1390 to your computer and use it in GitHub Desktop.
Save airhorns/36598799d44217365b31ad9c840b1390 to your computer and use it in GitHub Desktop.
/** Wrapper class that creates an introspectable promise that can be used to suspend */
export class Suspender<T> {
debug = false;
promise: Promise<T>;
finished = false;
rejected = false;
resolution: T | null = null;
rejectReason: any = null;
id = id++;
constructor(promise: Promise<T>) {
this.promise = promise
.then((result) => {
this.resolution = result;
this.debug && console.debug(`[suspender] ID=${this.id} resolving`, result, this);
return result;
})
.catch((reason) => {
this.rejected = true;
this.rejectReason = reason;
this.debug && console.debug(`[suspender] ID=${this.id} rejecting`, reason, this);
throw reason;
})
.finally(() => {
this.finished = true;
});
}
async get() {
return await this.promise;
}
getOrSuspend(): T {
if (this.finished) {
if (this.rejected) {
throw this.rejectReason;
} else {
return this.resolution as T;
}
}
this.debug && console.trace(`[suspender] ID=${this.id} suspending`, this);
throw this.promise;
}
}
// This would be nice, but doesn't work because the memo doesn't memoize if the component invoking this hook suspends
// That means a new Suspender is used every time, and getOrSuspend always suspends because the internal suspender state hasn't been set yet (which would happen on the next tick).
export const useSomeAsyncFunction = (key: string) => {
const suspender = useMemo(() => new Suspender(Fetcher.fetch(key)), [key]);
return suspender.getOrSuspend();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment