Skip to content

Instantly share code, notes, and snippets.

@sebald
Created May 22, 2018 11:14
Show Gist options
  • Save sebald/e4a5d5c23273b4493f4627b1e9fce058 to your computer and use it in GitHub Desktop.
Save sebald/e4a5d5c23273b4493f4627b1e9fce058 to your computer and use it in GitHub Desktop.
simple fetcher
export enum FetchStatus {
Init,
Fetching,
Aborted,
Success,
Error,
}
export type FetchEvent<T = any> =
| {
status: FetchStatus.Init;
data: null;
error: null;
}
| {
status: FetchStatus.Fetching;
data: T | null;
error: null;
}
| {
status: FetchStatus.Aborted;
data: T | null;
error: null;
}
| {
status: FetchStatus.Success;
data: T;
error: null;
}
| {
status: FetchStatus.Error;
data: null;
error: Error;
};
export interface Listener<T> {
(event: FetchEvent<T>): void;
}
export const createRequester = <T>() => {
let listeners: Listener<T>[] = [];
let controller: AbortController | null = null;
let latestAction: FetchEvent = {
status: FetchStatus.Init,
data: null,
error: null,
};
const emit = (event: FetchEvent<T>) => {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener(event);
}
};
const subscribe = (listener: Listener<T>) => {
let isSubscribed = true;
listeners.push(listener);
// Unsubscribe
return () => {
if (!isSubscribed) {
return;
}
isSubscribed = false;
const idx = listeners.indexOf(listener);
listeners.splice(idx, 1);
};
};
const request = (url: string, config?: RequestInit) => {
let signal: AbortSignal | undefined;
if (controller) {
controller.abort();
}
if (AbortController) {
controller = new AbortController();
signal = controller.signal;
}
emit({
status: FetchStatus.Fetching,
data: latestAction.data,
error: null,
});
return fetch(url, { ...config, signal })
.then(res => {
controller = null;
console.log(res);
// Non 2xx do not throw, see https://fetch.spec.whatwg.org/#fetch-method
return res.ok
? res.json()
: Promise.reject(new Error(res.statusText || String(res.status)));
})
.then(data => {
emit({ status: FetchStatus.Success, data, error: null });
return data;
})
.catch(error => {
emit(
error.name === 'AbortError'
? {
status: FetchStatus.Aborted,
data: latestAction.data,
error: null,
}
: { status: FetchStatus.Error, data: null, error }
);
return Promise.reject(error);
});
};
return { request, subscribe };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment