Skip to content

Instantly share code, notes, and snippets.

@fernandocamargo
Last active February 3, 2021 10:56
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 fernandocamargo/a91df8b0cc6508b62de28a27dc801ce5 to your computer and use it in GitHub Desktop.
Save fernandocamargo/a91df8b0cc6508b62de28a27dc801ce5 to your computer and use it in GitHub Desktop.
Trust no one

Usage:

import useAsync from './useAsync';

export const wait5Seconds = new Promise(
  resolve => window.setTimeout(resolve, 5000)
)

export default () => {
  const { resolve, loading } = useAsync({ promise: wait5Seconds })
  
  return (
    <button onClick={resolve} disabled={loading}>
      {loading ? 'Waiting...' : 'Wait'}
    </button>
  )
};
import noop from 'lodash/noop';
export const PENDING = new Promise(noop);
import { PENDING } from './constants';
export class Validity {
constructor() {
this.stale = false;
}
expire = () => {
this.stale = true;
};
succeed = (...params) => (!this.stale ? Promise.resolve(...params) : PENDING);
fail = (...params) => (!this.stale ? Promise.reject(...params) : PENDING);
check = promise => {
const { succeed, fail } = this;
return promise.then(succeed).catch(fail);
};
}
export const getInitialState = () => ({
loading: false,
data: null,
error: null,
});
export const attempt = () => () => ({ loading: true, data: null, error: null });
export const succeed = data => () => ({ loading: false, error: null, data });
export const fail = error => () => ({ loading: false, data: null, error });
import { useCallback, useEffect, useRef, useState } from 'react';
import { attempt, fail, getInitialState, succeed } from './reducers';
import { Validity } from './helpers';
export default ({ promise }) => {
const { current: controller } = useRef(new AbortController());
const [state, setState] = useState(getInitialState());
const resolve = useCallback(
(...params) => {
const { check, expire } = new Validity();
const abort = () => controller.abort();
new Promise((resolve, reject) =>
check(promise(...params))
.then(data => {
setState(succeed(data));
return resolve(data);
})
.catch(error => {
setState(fail(error));
return reject(error);
})
.finally(() => controller.signal.removeEventListener('abort', expire))
);
controller.signal.addEventListener('abort', expire);
setState(attempt({ params }));
return abort;
},
[promise, controller]
);
useEffect(() => () => controller.abort(), [controller]);
return { ...state, resolve };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment