Skip to content

Instantly share code, notes, and snippets.

@ravicious
Created January 3, 2025 14:38
Show Gist options
  • Save ravicious/e3764f4647c38f58d3afddbb40ba3311 to your computer and use it in GitHub Desktop.
Save ravicious/e3764f4647c38f58d3afddbb40ba3311 to your computer and use it in GitHub Desktop.
useAsync: andMap, map2, map3
describe('andMap', () => {
const tests: Array<{
statusA: AttemptStatus;
statusB: AttemptStatus;
expectedStatus: AttemptStatus;
}> = [
{
statusA: 'success',
statusB: 'success',
expectedStatus: 'success',
},
{
statusA: 'success',
statusB: 'error',
expectedStatus: 'error',
},
{
statusA: 'success',
statusB: 'processing',
expectedStatus: 'processing',
},
{
statusA: 'success',
statusB: '',
expectedStatus: '',
},
{
statusA: 'error',
statusB: 'success',
expectedStatus: 'error',
},
{
statusA: 'processing',
statusB: 'success',
expectedStatus: 'processing',
},
{
statusA: '',
statusB: 'success',
expectedStatus: '',
},
{
statusA: 'processing',
statusB: 'error',
expectedStatus: 'error',
},
{
statusA: '',
statusB: 'processing',
expectedStatus: 'processing',
},
];
test.each(tests)(
'andMap("$statusA", "$statusB") = "$expectedStatus"',
({ statusA, statusB, expectedStatus }) => {
const attemptA = makeAttemptFromStatus('foo', statusA);
const attemptB = makeAttemptFromStatus((a: string) => a, statusB);
expect(andMap(attemptA, attemptB).status).toEqual(expectedStatus);
}
);
});
/**
* andMap together with mapAttempt and currying allow mapping an arbitrary number of attempts in a
* type-safe way. Prefer using map2 and map3 where possible.
*/
export function andMap<A, B>(
wrappedValue: Attempt<A>,
wrappedFunction: Attempt<(dataA: A) => B>
): Attempt<B> {
if (
wrappedFunction.status === 'success' &&
wrappedValue.status === 'success'
) {
const func = wrappedFunction.data;
const value = wrappedValue.data;
return makeSuccessAttempt(func(value));
}
if (wrappedFunction.status === 'error') {
return wrappedFunction;
}
if (wrappedValue.status === 'error') {
return wrappedValue;
}
if (wrappedFunction.status === 'processing') {
return makeProcessingAttempt();
}
if (wrappedValue.status === 'processing') {
return makeProcessingAttempt();
}
if (wrappedFunction.status === '') {
return wrappedFunction;
}
if (wrappedValue.status === '') {
return wrappedValue;
}
}
/**
* map2 combines two attempts. The resulting attempt will be successful only if both attempts are
* successful. Otherwise the result will be the first encountered error, processing, or empty
* attempt, in that order.
*/
export function map2<A, B, C>(
attemptA: Attempt<A>,
attemptB: Attempt<B>,
mapFunc: (dataA: A, dataB: B) => C
): Attempt<C> {
const curriedMapFunc = (dataA: A) => (dataB: B) => mapFunc(dataA, dataB);
return andMap(attemptB, mapAttempt(attemptA, curriedMapFunc));
}
/**
* map3 combines three attempts. The resulting attempt will be successful only if all attempts are
* successful. Otherwise the result will be the first encountered error, processing, or empty
* attempt, in that order.
*/
export function map3<A, B, C, D>(
attemptA: Attempt<A>,
attemptB: Attempt<B>,
attemptC: Attempt<C>,
mapFunc: (dataA: A, dataB: B, dataC: C) => D
): Attempt<D> {
const curriedMapFunc = (dataA: A) => (dataB: B) => (dataC: C) =>
mapFunc(dataA, dataB, dataC);
return andMap(
attemptC,
andMap(attemptB, mapAttempt(attemptA, curriedMapFunc))
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment