Skip to content

Instantly share code, notes, and snippets.

@0xorial
Created February 23, 2021 17:23
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 0xorial/f59e32f612b1fc7cc1bc7b756b6d4c3c to your computer and use it in GitHub Desktop.
Save 0xorial/f59e32f612b1fc7cc1bc7b756b6d4c3c to your computer and use it in GitHub Desktop.
useAsyncEffect
import {useEffect, useRef, useState} from 'react';
export type AsyncEffectFuncContext = {
wrap: <T>(p: Promise<T>) => Promise<T>;
};
export type AsyncEffectFunc = (p: AsyncEffectFuncContext) => Promise<void>;
export function useAsyncEffect(
createGenerator: AsyncEffectFunc,
deps: React.DependencyList,
errorCallback: ((error: any) => void) | null = null
): {trigger: () => Promise<void>} {
const [counter, setCounter] = useState(0);
const pendingPromises = useRef(
new LinkedList<{
resolve: () => void;
reject: (e: Error) => void;
}>()
);
useEffect(() => {
let isCanceled = false;
const p = {
wrap: function<T>(t: Promise<T>) {
return new Promise<T>((resolve, reject) => {
t.then(x => {
if (!isCanceled) {
resolve(x);
}
}).catch(x => {
if (!isCanceled) {
reject(x);
}
});
});
}
};
createGenerator(p)
.then(() => {
// async calls should have been canceled through 'wrap' function
const promises = pendingPromises.current.toArray();
for (let promise of promises) {
promise.resolve();
}
})
.catch(e => {
console.error(e);
if (errorCallback) errorCallback(e);
const promises = pendingPromises.current.toArray();
for (let promise of promises) {
promise.reject(e);
}
});
return () => {
isCanceled = true;
};
}, [...deps, counter]);
return {
trigger: () => {
setCounter(x => x + 1);
return new Promise<void>((resolve, reject) => {
const r = pendingPromises.current.add({
resolve: () => {
r.detachSelf();
resolve();
},
reject: () => {
r.detachSelf();
reject();
}
});
});
}
};
}
export class HeadNode<T> {
public next: LinkedListNode<T> | TailNode<T>;
constructor() {
this.next = new TailNode(this);
}
}
// tslint:disable-next-line:max-classes-per-file
export class TailNode<T> {
public previous: LinkedListNode<T> | HeadNode<T>;
constructor(head: HeadNode<T>) {
this.previous = head;
}
}
// tslint:disable-next-line:max-classes-per-file
export class LinkedListNode<T> {
public next: LinkedListNode<T> | TailNode<T> | null = null;
public previous: LinkedListNode<T> | HeadNode<T> | null = null;
public readonly item: T;
constructor(item: T) {
this.item = item;
}
public detachSelf() {
if (!this.next && !this.previous) {
throw new Error('node is not attached');
}
if (this.next) {
this.next.previous = this.previous;
}
if (this.previous) {
this.previous.next = this.next;
}
this.next = null;
this.previous = null;
}
public attachAfter(node: LinkedListNode<T> | HeadNode<T>) {
if (this.next || this.previous) {
throw new Error('Node is inserted elsewhere');
}
this.next = node.next;
this.previous = node;
if (node.next) {
node.next.previous = this;
}
node.next = this;
}
public attachBefore(node: LinkedListNode<T> | TailNode<T>) {
if (!node.previous) {
throw new Error('no previous node found.');
}
this.attachAfter(node.previous);
}
public isAttached() {
return this.next !== undefined;
}
}
// tslint:disable-next-line:max-classes-per-file
export class LinkedList<T> {
public head: HeadNode<T>;
public tail: TailNode<T>;
constructor() {
this.head = new HeadNode<T>();
this.tail = this.head.next as TailNode<T>;
}
public add(item: T): LinkedListNode<T> {
const newNode = new LinkedListNode(item);
newNode.attachAfter(this.tail.previous);
return newNode;
}
public getItems(): T[] {
const result: T[] = [];
this.forEach(item => {
result.push(item);
});
return result;
}
public forEach(callback: (item: T, node: LinkedListNode<T>) => void) {
let current = this.head.next;
while (current !== this.tail) {
// if item is not tail it is always a node
const item = current as LinkedListNode<T>;
callback(item.item, item);
if (!item.next) {
throw new Error('badly attached item found.');
}
current = item.next;
}
}
public hasItems() {
return this.head.next !== this.tail;
}
public getLastItem() {
if (!this.hasItems()) {
throw new Error('no items in list.');
}
return this.head.next as LinkedListNode<T>;
}
toArray() {
const result: T[] = [];
this.forEach(x => {
result.push(x);
});
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment