Skip to content

Instantly share code, notes, and snippets.

@petsel
Last active July 4, 2025 11:30
Show Gist options
  • Save petsel/7f7e5b56f2bde78698474d0ad0b1fd4f to your computer and use it in GitHub Desktop.
Save petsel/7f7e5b56f2bde78698474d0ad0b1fd4f to your computer and use it in GitHub Desktop.
const defaultDescriptorOptions = { enumerable: false, writable: true, configurable: true };
const sealedDescriptorOptions = { enumerable: false, configurable: false };
/**
* Returns the internal `[[Class]]` tag of a value, like `[object String]` or returns `undefined`
* if no argument was passed.
*
* @param {...any} args
* A variadic argument list. The first argument (`args[0]`) is the optional `value` parameter.
* Its **presence** is detected via `args.length`, allowing the function to distinguish between
* an explicitly passed `undefined` value and a completely omitted argument.
* @returns {string | undefined}
* The value’s internal type signature, or the `undefined` value if no argument was passed.
*/
function getTypeSignature(...args) {
/** @type {any} */
const value = args[0];
return (args.length >= 1 && Object.prototype.toString.call(value)) || value;
}
/**
* Determines if a value is a function with standard `call`/`apply`/`bind` methods.
*
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is Function}
* A boolean value which indicates whether the tested type is any kind of function.
*/
function isFunction(value) {
return (
'function' === typeof value &&
'function' === typeof value.bind &&
'function' === typeof value.call &&
'function' === typeof value.apply
);
}
/**
* Determines if a value is a native async function.
*
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is (...args: any[]) => Promise<any>}
* A boolean value which indicates whether the tested value is a native async function.
*/
function isAsyncFunction(value) {
return getTypeSignature(value) === '[object AsyncFunction]';
}
/**
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is Promise}
* A boolean value which indicates whether the tested value is a promise.
*/
function isPromise(value) {
return getTypeSignature(value) === '[object Promise]';
}
/**
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is string}
* A boolean value which indicates whether the tested value is a string-type.
*/
function isString(value) {
return getTypeSignature(value) === '[object String]';
}
/**
* Every `safe` method handles the throwing of its operated function
* silently. Not every caught exception is an error type. In fact the
* exception or the throwing cause can be of any type.
* The custom error-type `SafeResultError` wraps any caught non-error
* exception into a distinct error instance which does refer the caught
* exception by its `cause` property.
*
* @extends {Error}
*/
class SafeResultError extends Error {
/**
* Does construct a new custom `SafeResultError` instance.
*
* @param {any} cause
* The passed error-cause value which can be of any type.
*/
constructor(cause) {
// const message = isString(cause)
// ? (!cause.trim() && 'Caught with a nonsense reason value.') || 'Caught with a reason.'
// : (!cause && 'Caught with a falsy exception.') || 'Caught with a non-error exception.'
const message = isString(cause)
? cause.trim() || 'Caught with an empty or blank string value.'
: (!cause && 'Caught with a falsy exception.') || 'Caught with a non-error exception.';
// re-use `Error::cause`.
super(message, { cause });
}
// shadow the `name` value.
get name() {
return 'SafeResultError';
}
}
// - Seales `SafeResultError` specific constructor properties in order to
// achieve a stable type-identity; the base of a reliable type-detection.
Object.defineProperty(SafeResultError.prototype, Symbol.toStringTag, {
get() { return 'SafeResultError'; }, ...sealedDescriptorOptions,
});
Object.defineProperty(SafeResultError.prototype, 'constructor', {
value: SafeResultError, ...sealedDescriptorOptions, writable: false,
});
Object.defineProperty(SafeResultError, 'name', {
value: 'SafeResultError', ...sealedDescriptorOptions, writable: false,
});
/**
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is SafeResultError}
* A boolean value which indicates whether the tested value is a custom `SafeResultError` type.
*/
function isSafeResultError(value) {
return (
Error.isError(value) &&
getTypeSignature(value) === '[object SafeResultError]' &&
Object.hasOwn(value, 'cause')
);
}
// // Extends the built-in `Error` type with another static method.
// Object.defineProperty(Error, 'isSafeResultError', {
// value: isSafeResultError, ...defaultDescriptorOptions,
// });
/**
* @typedef {SafeResultError | Error} SafeResultFailType
*/
/**
* @typedef {any} SafeResultPassType
*/
/**
* @typedef {(value: SafeResultFailType | SafeResultPassType) => SafeResultPassType | Promise<SafeResultPassType> | void | Promise<void>} safeResultFlowCallback
*/
/**
* @typedef {SafeResult<SafeResultFailType | null, SafeResultPassType | null>} SafeResultTuple
*/
/**
* @typedef {SafeAsyncResult<SafeResultTuple>} SafeAsyncResultTuple
*/
/**
* A `SafeResult` type represents the result of a potentially
* fallible operation. It stores both the potential error and
* the result's value in a tuple-like structure and prevents
* the latter's mutation.
* `SafeResult` furthermore does extend the native `Array`,
* primarily for interoperability, but does freeze the
* structure of each of its instances.
*
* @template E - Type of the error.
* @template T - Type of the result value.
* @extends {Array<[ E | null, T | null ]>}
*/
class SafeResult extends Array {
/**
* Does construct a new `SafeResult` instance.
*
* @param {E | null} error
* The error value (if any) of the potentially failed operation.
* @param {T | null} value
* The return value (if any) of the potentially successful operation.
*/
constructor(error, value) {
super(error, value);
// Assure an immutable `SafeResult` instance.
Object.freeze(this);
}
/**
* Ensures that array methods return plain arrays (not `SafeResult` instances).
* @returns {typeof Array}
*/
static get [Symbol.species]() {
return Array;
}
/**
* The `error` property of the safe result type, exposed via a getter.
* @returns {E | null}
*/
get error() { return this[0]; }
/**
* The `value` property of the safe result type, exposed via a getter.
* @returns {T | null}
*/
get value() { return this[1]; }
/**
* @param {safeResultFlowCallback} callback
* The piped/chained function/method which is going to get handled safely.
* @param {any} [target]
* The optional `this` context of the chained and safely to be executed method.
* @returns {SafeResultTuple | SafeAsyncResultTuple}
* A `SafeResult` tuple/array either as is (as result of a non-async function call)
* or wrapped into a custom promise (for an async function call).
* The safe result's first item exclusively is either `null` or any kind of `Error`
* instance (including the `SafeResultError` type); its second item - the function's
* actually returned value - can be of any type.
*/
pass(callback, target) {
const result = this;
return (
isSafeResult(result)
// handle happy `then`-like `pass`-path, or keep safe-result identity for handling `fail`.
? ((result.error === null) && handleSafely(callback, target, result.value)) || result
// happy-path exception-handling.
: new SafeResult(
new TypeError(
'`SafeResult.prototype.pass` has to be invoked exclusively within the context of a `SafeResult` instance.'
), null,
)
);
// let result = this;
//
// if (isSafeResult(result)) {
// debugger;
//
// const [error, value] = result;
//
// result = (error === null) && handleSafely(callback, target, value) || result;
// } else {
// debugger;
//
// result = new SafeResult(
// new TypeError(
// '`SafeResult.prototype.pass` has to be invoked exclusively within the context of a `SafeResult` instance.'
// ), null,
// );
// }
// return result;
}
/**
* @param {safeResultFlowCallback} callback
* The piped/chained function/method which is going to get handled safely.
* @param {any} [target]
* The optional `this` context of the chained and safely to be executed method.
* @returns {SafeResultTuple | SafeAsyncResultTuple}
* A `SafeResult` tuple/array either as is (as result of a non-async function call)
* or wrapped into a custom promise (for an async function call).
* The safe result's first item exclusively is either `null` or any kind of `Error`
* instance (including the `SafeResultError` type); its second item - the function's
* actually returned value - can be of any type.
*/
fail(callback, target) {
const result = this;
return (
isSafeResult(result)
// handle happy `then`-like `fail`-path, or keep safe-result identity for handling `pass`.
? ((result.error !== null) && handleSafely(callback, target, result.error)) || result
// happy-path exception-handling.
: new SafeResult(
new TypeError(
'`SafeResult.prototype.fail` has to be invoked exclusively within the context of a `SafeResult` instance.'
), null,
)
);
// let result = this;
//
// if (isSafeResult(result)) {
// debugger;
//
// const [error, value] = result;
//
// result = (error !== null) && handleSafely(callback, target, error) || result;
// } else {
// debugger;
//
// result = new SafeResult(
// new TypeError(
// '`SafeResult.prototype.fail` has to be invoked exclusively within the context of a `SafeResult` instance.'
// ), null,
// );
// }
// return result;
}
}
// - Seales `SafeResult` specific constructor properties in order to achieve
// a stable type-identity; the base of a reliable type-detection.
Object.defineProperty(SafeResult.prototype, Symbol.toStringTag, {
get() { return 'SafeResult'; }, ...sealedDescriptorOptions,
});
Object.defineProperty(SafeResult.prototype, 'constructor', {
value: SafeResult, ...sealedDescriptorOptions, writable: false,
});
Object.defineProperty(SafeResult, 'name', {
value: 'SafeResult', ...sealedDescriptorOptions, writable: false,
});
/**
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is SafeResult}
* A boolean value which indicates whether the tested value is a `SafeResult` array/tuple.
*/
function isSafeResult(value) {
let proto;
return (
Array.isArray(value) &&
value.length === 2 &&
Object.keys(value).length === 2 &&
getTypeSignature(value) === '[object SafeResult]' &&
!!(proto = Object.getPrototypeOf(value)) &&
Object.hasOwn(proto, 'error') &&
Object.hasOwn(proto, 'value')
);
}
// const promisePrototype = {
// then: { writable: true, enumerable: false, configurable: true, value: ƒ },
// catch: { writable: true, enumerable: false, configurable: true, value: ƒ },
// finally: { writable: true, enumerable: false, configurable: true, value: ƒ },
// Symbol(Symbol.toStringTag): { value: 'Promise', writable: false, enumerable: false, configurable: true },
// constructor: { writable: true, enumerable: false, configurable: true, value: ƒ },
// };
function handleSafeAsyncResultReject(/* reason */) {
debugger;
console.log('... safe-async-result :: rejectc ...', { args: arguments });
throw new SafeResultError(
new ReferenceError(
'A `SafeAsyncResult` does not handle `reject`. It allways follows the happy `resolve` path.',
),
);
}
function handleSafeAsyncResultPass(callback, target, safeResult) {
return (
isSafeAsyncResult(safeResult)
// unwrap. safeguard ... actively assures an always flat (hence un-nested) safe-async-result.
? safeResult.pass(callback, target)
// handle happy `then`-like `pass`-path.
: (safeResult.error === null) && handleSafely(callback, target, safeResult.value)
) || safeResult; // ... keep safe-result identity otherwise.
// let result;
//
// if (isSafeAsyncResult(safeResult)) {
//
// result = safeResult.pass(callback, target);
//
// } else {
// const [error, value] = safeResult;
//
// if (error === null) {
//
// result = handleSafely(callback, target, value);
//
// // does cause a nested safe-async-result. skip this condition.
// // if (isSafeResult(result)) {
// // result = createSafeAsyncResultFromSafeResult(result);
// // }
// }
// }
// return result || safeResult;
}
function handleSafeAsyncResultFail(callback, target, safeResult) {
return (
isSafeAsyncResult(safeResult)
// unwrap. safeguard ... actively assures an always flat (hence un-nested) safe-async-result.
? safeResult.fail(callback, target)
// handle happy `then`-like `fail`-path.
: (safeResult.error !== null) && handleSafely(callback, target, safeResult.error)
) || safeResult; // ... keep safe-result identity otherwise.
// let result;
//
// if (isSafeAsyncResult(safeResult)) {
//
// result = safeResult.fail(callback, target);
//
// } else {
// const [error, value] = safeResult;
//
// if (error !== null) {
//
// result = handleSafely(callback, target, error);
//
// // does cause a nested safe-async-result. skip this condition.
// // if (isSafeResult(result)) {
// // result = createSafeAsyncResultFromSafeResult(result);
// // }
// }
// }
// return result || safeResult;
}
class SafeAsyncResult extends Promise {
constructor(callback) {
debugger;/*
if (isFunction(callback)) {
callback = (superCallback => {
debugger;
// - `reject` does not need to be handled.
// - `SafeAsyncResult` does always resolve.
return function (resolve/*, reject* /) {
debugger;
console.log('... safe-async-result :: super-callback ...');
return superCallback(resolve, handleSafeAsyncResultReject);
};
})(callback);
}*/
// super(
// isFunction(callback)
// && (superCallback => (resolve => handleSafely(superCallback, null, resolve)))(callback)
// || callback
// );
super(callback);
}
/**
* @param {safeResultFlowCallback} callback
* The piped/chained function/method which is going to get handled safely.
* @param {any} [target]
* The optional `this` context of the chained and safely to be executed method.
* @returns {SafeResultTuple | SafeAsyncResultTuple}
* A `SafeResult` tuple/array either as is (as result of a non-async function call)
* or wrapped into a custom promise (for an async function call).
* The safe result's first item exclusively is either `null` or any kind of `Error`
* instance (including the `SafeResultError` type); its second item - the function's
* actually returned value - can be of any type.
*/
pass(callback, target) {
// debugger;
// - `SafeAsyncResult.prototype.pass` has to be implemented non-async.
// - Thus, one has to use its _**Thenable`**_ trait via `Promise.prototype.then`.
return ((isSafeAsyncResult(this) && Promise.prototype.then.call(
this, handleSafeAsyncResultPass.bind(null, callback, target),
)) || createSafeAsyncResultFromPromise(
Promise.resolve(
new SafeResult(
new TypeError(
'`SafeAsyncResult.prototype.pass` has to be invoked exclusively within the context of a `SafeAsyncResult` instance.'
), null,
),
),
));
}
/**
* @param {safeResultFlowCallback} callback
* The piped/chained function/method which is going to get handled safely.
* @param {any} [target]
* The optional `this` context of the chained and safely to be executed method.
* @returns {SafeResultTuple | SafeAsyncResultTuple}
* A `SafeResult` tuple/array either as is (as result of a non-async function call)
* or wrapped into a custom promise (for an async function call).
* The safe result's first item exclusively is either `null` or any kind of `Error`
* instance (including the `SafeResultError` type); its second item - the function's
* actually returned value - can be of any type.
*/
fail(callback, target) {
// debugger;
// - `SafeAsyncResult.prototype.fail` has to be implemented non-async.
// - Thus, one has to use its _**`Thenable`**_ trait via `Promise.prototype.then`.
return ((isSafeAsyncResult(this) && Promise.prototype.then.call(
this, handleSafeAsyncResultFail.bind(null, callback, target),
)) || createSafeAsyncResultFromPromise(
Promise.resolve(
new SafeResult(
new TypeError(
'`SafeAsyncResult.prototype.fail` has to be invoked exclusively within the context of a `SafeAsyncResult` instance.'
), null,
),
),
));
}
get then() { return void 0; }
get catch() { return void 0; }
get finally() { return void 0; }
}
// - Seales `SafeAsyncResult` specific constructor properties in order to achieve
// a stable type-identity; the base of a reliable type-detection.
Object.defineProperty(SafeAsyncResult.prototype, Symbol.toStringTag, {
get() { return 'SafeAsyncResult'; }, ...sealedDescriptorOptions,
});
Object.defineProperty(SafeAsyncResult.prototype, 'constructor', {
value: SafeAsyncResult, ...sealedDescriptorOptions, writable: false,
});
Object.defineProperty(SafeAsyncResult, 'name', {
value: 'SafeAsyncResult', ...sealedDescriptorOptions, writable: false,
});
/**
* @param {any} [value]
* An optionally passed value of any type.
* @returns {value is SafeAsyncResult}
* A boolean value which indicates whether the tested value is a `SafeAsyncResult` array/tuple.
*/
function isSafeAsyncResult(value) {
let proto;
return (
getTypeSignature(value) === '[object SafeAsyncResult]' &&
!!(proto = Object.getPrototypeOf(value)) &&
isPromise(Object.getPrototypeOf(proto)) &&
Object.hasOwn(proto, 'pass') &&
Object.hasOwn(proto, 'fail') &&
isFunction(value.pass) &&
isFunction(value.fail)
);
}
// on ... "handle safely" versus "process safely"
//
// - With a utility-function at framework or runtime-level, one might want to avoid reusing
// the ubiquitous "handle". Then `processSafely` might be preferred over `handleSafely`.
// The former in addition comes with a slightly more data/flow-processing vibe.
//
// - In case one exclusively does focus on a name that cleanly fits both promises and functions
// and is idiomatic to JavaScript/TypeScript, one should just go with `handleSafely`.
//
function createSafeAsyncResultFromSafeResult(result) {
// debugger;
return new SafeAsyncResult(resolve => resolve(result));
}
function createSafeAsyncResultFromPromise(proceed) {
// debugger;
return new SafeAsyncResult(async (resolve) => {
let value = null;
let error = null;
// debugger;
try {
value = await proceed;
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
resolve(new SafeResult(error, value));
});
}
function createSafeAsyncResultFromAsyncFunction(proceed, target, ...args) {
// debugger;
return new SafeAsyncResult(async (resolve) => {
let value = null;
let error = null;
// debugger;
try {
value = await proceed.apply(target ?? null, args);
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
resolve(new SafeResult(error, value));
});
}
/**
* Handles/processes any function/method (async or not) and any promise safely
* whilst preserving a method's `this` context.
* The possible throwing of a processed function/promise gets handled silently
* and is reflected by the always returned structured `SafeResult` array/tuple.
* The latter gets returned either as is (as result of a non-async function call),
* or it gets wrapped into a custom promise (reflecting the processing of either an async
* function or a promise).
*
* @proceed {SafeResult | SafeAsyncResult | Promise | AsyncFunction | Function}
* The function/method or (custom) promise which is going to get handled safely.
* @param {any} target
* The `this` context of the safely to be executed/handled method.
* @param {...any} args
* The arguments of the safely to be executed/handled function/method.
* @returns {SafeResultTuple | SafeAsyncResultTuple}
* A `SafeResult` tuple/array either as is (as result of a non-async function call)
* or wrapped into a custom promise (having processed either an async function or a promise).
* The safe result's first item exclusively is either `null` or any kind of `Error`
* instance (including the `SafeResultError` type); its second item - the function's
* or the promise's actually returned respectively resolved value - can be of any type.
*/
function handleSafely(proceed, target, ...args) {
let result = null;
if (isFunction(proceed)) {
if (!isAsyncFunction(proceed)) {
// debugger;
let value = null;
let error = null;
try {
value = proceed.apply(target ?? null, args);
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
if (isPromise(value)) {
// debugger;
result = createSafeAsyncResultFromPromise(value);
} else {
// debugger;
result = new SafeResult(error, value);
}
} else {
// debugger;
result = createSafeAsyncResultFromAsyncFunction(proceed, target, ...args);
}
} else if (isPromise(proceed)) {
// debugger;
result = createSafeAsyncResultFromPromise(proceed);
} else if (
isSafeResult(proceed) ||
isSafeAsyncResult(proceed)
) {
// debugger;
// -- IDENTITY --
result = proceed;
} else {
// debugger;
result = new SafeResult(
new TypeError(
'`handleSafely` does process exclusively promises and callable function types, async or non-async alike, as well as safe-result instances, either async or not.'
), null,
);
}
debugger;
return result;
}
/**
* Executes any non-async function/method safely whilst preserving its `this` context.
* The possible throwing of an executed function gets handled silently and reflected
* by the always returned structured `SafeResult` array/tuple.
*
* @this {Function}
* The function/method which gets operated/executed by `safe`.
* @param {any} target
* An additionally to be passed target, the `this` context of
* the safely to be executed method.
* @param {...any} args
* The arguments of the safely to be executed function/method.
* @returns {SafeResultTuple}
* Always a `SafeResult` tuple/array where its first item exclusively is either `null`
* or any kind of `Error` instance (including the `SafeResultError` type), and where
* its second item - the function's actually returned result - can be of any type.
*/
function safe(target, ...args) {
/** @type {Function} */
const proceed = this;
let result = null;
let error = null;
if (isFunction(proceed) && !isAsyncFunction(proceed)) {
try {
result = proceed.apply(target ?? null, args);
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
} else {
// improper usage, like an explicit delegation to a wrong type, gets thrown immediately.
throw new TypeError(
'`Function.prototype.safe` has to be exclusively invoked at a non-async function type.'
);
}
return new SafeResult(error, result);
}
/**
* Executes any callable function/method asynchronously and safely whilst preserving
* its `this` context.
* The possible throwing of an executed function gets handled silently and reflected
* by the always returned structured `SafeResult` array/tuple.
*
* @this {AsyncFunction | Function}
* The function/method which gets asynchronously operated/executed by `safeAsync`.
* @param {any} target
* An additionally to be passed target, the `this` context of the asynchronously
* and safely to be executed method.
* @param {...any} args
* The arguments of the asynchronously and safely to be executed function/method.
* @returns {SafeAsyncResultTuple}
* Always a resolved promise which wraps a `SafeResult` tuple/array where its first
* item exclusively is either `null` or any kind of `Error` instance (including the
* `SafeResultError` type), and where its second item - the function's actually
* returned result - can be of any type.
*/
async function safeAsync(target, ...args) {
/** @type {AsyncFunction | Function} */
const proceed = this;
let result = null;
let error = null;
if (isFunction(proceed)) {
try {
result = await proceed.apply(target ?? null, args);
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
} else {
// improper usage, like an explicit delegation to a wrong type, gets thrown immediately.
throw new TypeError(
'`AsyncFunction.prototype.safe` has to be exclusively invoked at a function type.'
);
}
// The return value automatically gets wrapped into a resolved promise.
// It's the implicit form of the very explicit ...
// `return Promise.resolve(new SafeResult(error, result));`
return new SafeResult(error, result);
}
/**
* Awaits/handles any promise safely. The possible throwing of an awaited promise gets
* handled silently and reflected by the always returned structured `SafeResult` array.
*
* @returns {SafeAsyncResultTuple}
* Always a resolved promise which wraps a `SafeResult` tuple/array where its first
* item exclusively is either `null` or any kind of `Error` instance (including the
* `SafeResultError` type), and where its second item - the promise's actually
* resolved value - can be of any type.
*/
async function safeAwait() {
/** @type {Promise} */
const promise = this;
let resolved = null;
let error = null;
if (isPromise(promise)) {
try {
resolved = await promise;
} catch (cause) {
error = (Error.isError(cause) && cause) || new SafeResultError(cause);
}
} else {
// improper usage, like an explicit delegation to a wrong type, gets thrown immediately.
throw new TypeError(
'`Promise.prototype.safe` has to be exclusively invoked at a `Promise` instance.'
);
}
// The return value automatically gets wrapped into a resolved promise.
// It's the implicit form of the very explicit ...
// `return Promise.resolve(new SafeResult(error, resolved));`
return new SafeResult(error, resolved);
}
// Rewrites and seales the `safe` function's name property in order to support a stable type-detection.
Object.defineProperty(safe, 'name', {
value: 'safe', ...sealedDescriptorOptions, writable: false,
});
// Extends `Function.prototype` with a `safe` method that returns a `SafeResult`.
Object.defineProperty(Function.prototype, 'safe', {
value: safe, ...defaultDescriptorOptions,
});
// Rewrites and seales the `safeAsync` function's name property in order to support a stable type-detection.
Object.defineProperty(safeAsync, 'name', {
value: 'safe', ...sealedDescriptorOptions, writable: false,
});
// Extends `AsyncFunction.prototype` with a `safe` method that returns a `SafeResult`.
Object.defineProperty((async function () {}).constructor.prototype, 'safe', {
value: safeAsync, ...defaultDescriptorOptions,
});
// Rewrites and seales the `safeAwait` function's name property in order to support a stable type-detection.
Object.defineProperty(safeAwait, 'name', {
value: 'safe', ...sealedDescriptorOptions, writable: false,
});
// Extends `Promise.prototype` with a `safe` method that returns a `SafeResult`.
Object.defineProperty(Promise.prototype, 'safe', {
value: safeAwait, ...defaultDescriptorOptions,
});
// ----- ----- ----- ----- -----
const [fetchException, fetchResponse] =
await handleSafely(fetch, null, 'https://jsonplaceholder.typicode.com/posts');
const [error, data] = (fetchException === null)
&& (
fetchResponse.ok
&& await handleSafely(fetchResponse.json())
|| (
fetchResponse.status === 404
&& new SafeResult(new NotFoundError(fetchResponse), null)
|| new SafeResult(new HttpError(fetchResponse), null)
)
) || new SafeResult(fetchException, fetchResponse);
console.log({ error, data });
const [cause, value] = await handleSafely(
fetch, null, 'https://jsonplaceholder.typicode.com/posts'
).pipe(async (response) => {
if (response.ok) {
return await response.json();
} else if (response.status === 404) {
throw new NotFoundError(response);
} else {
throw new HttpError(response);
}
})
// safe-result.d.ts
/**
* Returns the internal [[Class]] tag of a value, like `[object String]`.
* Returns `undefined` if no argument is passed.
*
* @param args - A variadic argument list.
*/
export function getTypeSignature(...args: any[]): string | undefined;
/**
* Determines if a value is a function with standard call/apply/bind methods.
*/
export function isFunction(value?: any): value is Function;
/**
* Determines if a value is a native async function.
*/
export function isAsyncFunction(value?: any): value is (...args: any[]) => Promise<any>;
/**
* Determines if a value is a Promise instance.
*/
export function isPromise(value?: any): value is Promise<any>;
/**
* Determines if a value is a string (primitive or object-wrapped).
*/
export function isString(value?: any): value is string;
/**
* Determines if a value is a custom `SafeResultError` instance.
*/
export function isSafeResultError(value?: any): value is SafeResultError;
/**
* Determines if a value is a `SafeResult` instance.
*/
export function isSafeResult(value?: any): value is SafeResult<Error | SafeResultError | null, any>;
/**
* Custom error class that wraps non-error exceptions with a cause.
*/
export class SafeResultError extends Error {
constructor(cause: any);
get name(): 'SafeResultError';
}
/**
* Represents the result of a potentially fallible operation.
* It stores both an error and a value, and is immutable.
*
* @template E - Type of the error.
* @template T - Type of the result value.
*/
export class SafeResult<E = Error | SafeResultError | null, T = any> extends Array<[E | null, T | null]> {
constructor(error: E | null, value: T | null);
get error(): E | null;
get value(): T | null;
static readonly [Symbol.species]: typeof Array;
}
/**
* Shorthand alias for the common SafeResult tuple structure.
*/
export type SafeResultTuple = SafeResult<SafeResultError | Error | null, any>;
/**
* Safely executes any non-async function with a preserved `this` context.
*
* This method is intended to be used via `Function.prototype.safe`.
*
* @param target - The value to use as `this`.
* @param args - Arguments passed to the function.
* @returns A `SafeResultTuple` containing error and result.
*/
export function safe(this: Function, target?: any, ...args: any[]): SafeResultTuple;
/**
* Safely executes an async or sync function with a preserved `this` context.
*
* This method is intended to be used via `AsyncFunction.prototype.safe`.
*
* @param target - The value to use as `this`.
* @param args - Arguments passed to the function.
* @returns A Promise resolving to a `SafeResultTuple`.
*/
export function safeAsync(this: Function, target?: any, ...args: any[]): Promise<SafeResultTuple>;
/**
* Safely awaits any Promise.
*
* This method is intended to be used via `Promise.prototype.safe`.
*
* @returns A Promise resolving to a `SafeResultTuple`.
*/
export function safeAwait(this: Promise<any>): Promise<SafeResultTuple>;
// Prototype extensions for IDE support
declare global {
interface Function {
safe: typeof safe;
}
interface Promise<T> {
safe: typeof safeAwait;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment