0.0.1-experimental.5 • Published 6 months ago

@attio/fetchable v0.0.1-experimental.5

Weekly downloads
-
License
-
Repository
-
Last release
6 months ago

Fetchable

fetchable is a library for explicitly handling errors and loading in Typescript and JavaScript.

Installation

> npm install @attio/fetchable

Concepts

Primitive states

There are three primitive states in fetchable.

✅ Complete

A Complete<V> is a simple JavaScript object which represents any kind of intact data.

import {complete} from "@attio/fetchable"

const completeNumber = complete(1) // Returns a Complete<1>

console.log(completeNumber.value) // 1
console.log(completeNumber.state) // "complete"

❌ Errored

An Errored<E> is another JavaScript object which represents something that went wrong.

import {errored} from "@attio/fetchable"

const erroredThing = errored("Oops") // Returns an Errored<"Oops">

console.log(erroredThing.error) // "Oops"
console.log(erroredThing.state) // "errored"

⏳ Pending

A Pending is another JavaScript object which represents some process which is in progress.

import {pending} from "@attio/fetchable"

const pendingThing = pending() // Returns a Pending

console.log(pendingThing.state) // "pending"

Higher order states

The fun comes when we start to mix up the three primitive states.

Result: Complete or Errored

A Result<V, E> is an object which is either a Complete<V> or an Errored<E>. It represents the outcome of a process which might succeed or fail.

import {isComplete} from "@attio/fetchable"

const result = doSynchronousThingWhichMightError() // Returns Result<number, "Oops">

if (isComplete(result)) {
    console.log(result.value) // number
} else {
    console.log(result.error) // "Oops"
}

Loadable: Complete or Pending

A Loadable<V> is an object which is either a Complete<V> or a Pending. It represents the outcome of an asynchronous process which cannot fail.

import {isComplete} from "@attio/fetchable"

const loadable = getStatusOfLongRunningProcess() // Returns Loadable<number>

if (isComplete(loadable)) {
    console.log(loadable.value) // number
}

Fetchable: Complete or Pending or Errored

A Fetchable<V, E> is an object which is either a Complete<V>, Errored<E> or Pending. It represents the outcome of an asynchronous process which can fail.

Fetchable is most often seen in declarative front-end code where we don't want to expose promises.

import {isComplete, isErrored} from "@attio/fetchable"

const fetchable = getStatusOfLongRunningProcessWhichMightFail() // Returns Fetchable<number, "Oops">

if (isComplete(fetchable)) {
    console.log(fetchable.value) // number
} else if (isErrored(fetchable)) {
    console.log(fetchable.error) // "Oops"
}

AsyncResult

An AsyncResult<V, E> is a promise of a Result<V, E>. Complete<V>, Errored<E> or Pending. It is most often seen in imperative code where we use promises for asynchronous processes.

import {isComplete, isErrored} from "@attio/fetchable"

const asyncResult = runAsyncProcessWhichMightFail() // Returns AsyncResult<number, "Oops">

asyncResult.then(result => {
    if (isComplete(result)) {
        console.log(result.value) // number
    } else if (isErrored(result)) {
        console.log(result.error) // "Oops"
    }
})

Transformations

fetchable provides functional utilities for working with these objects without explicitly checking which state they're in.

map

import {map} from "@attio/fetchable"

const fetchable = getStatusOfLongRunningProcessWhichMightFail() // Returns Fetchable<number, "Oops">

// Increments the fetchable if it's complete. 
// Returns Fetchable<number, "Oops">
const incrementedFetchable = map(fetchable, (number) => number + 1)

bind

import {map} from "@attio/fetchable"

const fetchable = getStatusOfLongRunningProcessWhichMightFail() // Returns Fetchable<number, "Oops">

// Increments the fetchable if it's complete and not zero.
// Returns Fetchable<number, "Oops" | "Unexpected zero">
const incrementedFetchable = bind(fetchable, (number) =>
    number === 0 ? errored("Unexpected zero") : complete(number + 1)
)

combine

Arrays

import {combine} from "@attio/fetchable"

const fetchable1 = getStatusOfLongRunningProcessWhichMightFail() // Returns Fetchable<number, "Oops">
const fetchable2 = doSynchronousThingWhichMightError() // Returns Result<string, "Eek">

// If both fetchables are complete then their values will be returned in a complete array.
// Returns Fetchable<[number, string], "Oops" | "Eek">
const combinedFetchable = combine([fetchable1, fetchable2])

Objects

import {combine} from "@attio/fetchable"

const fetchableA = getStatusOfLongRunningProcessWhichMightFail() // Returns Fetchable<number, "Oops">
const fetchableB = doSynchronousThingWhichMightError() // Returns Result<string, "Eek">

// If both fetchables are complete then their values will be returned in a new object.
// Returns Fetchable<{a: number, b: string}, "Oops" | "Eek">
const combinedFetchable = combine({a: fetchable1, b: fetchable2})

combineAsync

Like Promise.all for AsyncResults. It takes multiple AsyncResults and resolves when they all resolve with Completes or when one resolves with an Errored.

Arrays

import {combineAsync} from "@attio/fetchable"

const asyncResult1 = runAsyncProcessWhichMightFail() // Returns AsyncResult<number, "Oops">
const asyncResult2 = runAnotherAsyncProcessWhichMightFail() // Returns AsyncResult<string, "Eek">

// Returns Result<[number, string], "Oops" | "Eek">
const combinedResult = await combineAsync([asyncResult1, asyncResult2])

Objects

import {combine} from "@attio/fetchable"

const asyncResultA = runAsyncProcessWhichMightFail() // Returns AsyncResult<number, "Oops">
const asyncResultB = runAnotherAsyncProcessWhichMightFail() // Returns AsyncResult<string, "Eek">

// If both fetchables are complete then their values will be returned in a new object.
// Returns Result<{a: number, b: string}, "Oops" | "Eek">
const combinedResult = await combineAsync({a: asyncResultA, b: asyncResultB})