Skip to content

Instantly share code, notes, and snippets.

@heyimalex
Last active June 21, 2019 19:45
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 heyimalex/bdf0f3fc90d8fb755a2e29ca4d6931db to your computer and use it in GitHub Desktop.
Save heyimalex/bdf0f3fc90d8fb755a2e29ca4d6931db to your computer and use it in GitHub Desktop.
Primitives for representing promise states
// PromiseState provides primitives for representing a promise's "state" as a
// value.
//
// That's kinda abstract so let's think through it. At any given point in time
// a promise is either pending, resolved, or rejected. There's no real
// synchronous way to access this promise "state", promises only give us `then`
// and callbacks.
//
// In react we usually care about rendering _all_ of these possible states. For
// example, we want to show a spinner when it's loading, the results when it
// succeeds, or an error message when it fails. The normal pattern is to track
// all of this in state: you setState({ pending: true }), make the request, and
// then setState({ pending: false, result: value }) in the promise callback.
//
// In my experience, every component that does this has its own little quirks;
// some call the state variable "pending", some call it "loading", "results" vs
// "value", "error" vs "reason". In almost every case you're adding some
// ambiguous sounding key to the top level state; god forbid you need to track
// the state of _two_ promises in a single component. Now your state has
// fooPending, barPending, fooResult, barResult, fooError, barError. You need
// to remember to clear errors and results every time the promise gets
// "replaced". Tracking whether a request has been made at all also needs to be
// taken care of. And the whole thing is a pain to use with typescript: we
// _know_ that when pending is false results is defined, but we still need to
// do undefined checks to satisfy the compiler.
//
// This file is a solution to that problem. It doesn't _handle_ anything
// relating to promises per-se, but it gives very convenient primitives for
// _representing_ their state as values in a way that pairs very nicely with
// typescript.
interface PromiseStatePending<M> {
status: "pending";
pending: true;
fulfilled: false;
rejected: false;
value: null;
reason: null;
meta: M;
}
interface PromiseStateFulfilled<V, M> {
status: "fulfilled";
pending: false;
fulfilled: true;
rejected: false;
value: V;
reason: null;
meta: M;
}
interface PromiseStateRejected<R, M> {
status: "rejected";
pending: false;
fulfilled: false;
rejected: true;
value: null;
reason: R;
meta: M;
}
type PromiseState<V = any, M = undefined, R = any> =
| PromiseStatePending<M>
| PromiseStateFulfilled<V, M>
| PromiseStateRejected<R, M>;
function create(meta?: undefined): PromiseStatePending<undefined>;
function create<M>(meta: M): PromiseStatePending<M>;
function create<M>(meta: M): PromiseStatePending<M> {
return {
status: "pending",
pending: true,
fulfilled: false,
rejected: false,
value: null,
reason: null,
meta: meta
};
}
function resolve<V>(
value: V,
meta?: undefined
): PromiseStateFulfilled<V, undefined>;
function resolve<V, M>(value: V, meta: M): PromiseStateFulfilled<V, M>;
function resolve<V, M>(value: V, meta: M): PromiseStateFulfilled<V, M> {
return {
status: "fulfilled",
pending: false,
fulfilled: true,
rejected: false,
value: value,
reason: null,
meta: meta
};
}
function reject<R>(
reason: R,
meta?: undefined
): PromiseStateRejected<R, undefined>;
function reject<R, M>(reason: R, meta: M): PromiseStateRejected<R, M>;
function reject<R, M>(reason: R, meta: M): PromiseStateRejected<R, M> {
return {
status: "rejected",
pending: false,
fulfilled: false,
rejected: true,
value: null,
reason: reason,
meta: meta
};
}
const PromiseState = {
create,
resolve,
reject
};
export default PromiseState;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment