Peter Seliger
2 min readJan 4, 2025

--

The author fails to acknowledge that Go’s approach to error handling, which forms a core feature of the language, likely inspired the article’s subject/idea. Furthermore, there is no mention that the article’s topic might have been influenced or borrowed from an early proposal for a safe assignment operator (?=), which has not yet been officially adopted.

Additionally, the solution proposed by the author requires two separate functions: safeAssign and safeAssignAsync. This approach is less than ideal as it does not provide a unified solution capable of handling both synchronous and asynchronous functions seamlessly.

For those who do not wish to wait for the potential introduction of the ?= operator—which might never come—a better alternative would be to implement a prototype-based method. This method could be similar to existing ones like Function.prototype.call and Function.prototype.apply.

It could be named, for instance, execute, and implemented as Function.prototype.execute for synchronous functions and AsyncFunction.prototype.execute for asynchronous ones.

A ready-to-use implementation might look as follows:

function exposeTypeSignature(value) {
return Object.prototype.toString.call(value);
}

function isAsyncFuntionType(value) {
return exposeTypeSignature(value) === '[object AsyncFunction]';
}
function isFunction(value) {
return (
typeof value === 'function' &&
typeof value.call === 'function' &&
typeof value.apply === 'function'
);
}

function execute/*Safely*/(target, ...args) {
const proceed = this;

let result = null;
let error = null;

if (isFunction(proceed)) {
if (isAsyncFuntionType(proceed)) {

error = new TypeError(
'The non-async `execute` exclusively can be invoked at non-async callable types.'
);
} else {
try {
result = proceed.apply(target ?? null, args);

} catch (/** @type {Error} */exception) {

error = exception;
}
}
} else {
error = new TypeError(
'`execute` exclusively can be invoked at a callable type.'
);
}
return [/** @type {null|Error} */error, result];
}
async function executeAsync/*Safely*/(target, ...args) {
const proceed = this;

let result = null;
let reason = null;
let error = null;

if (isFunction(proceed)) {
try {
result = await proceed.apply(target ?? null, args);

} catch (/** @type {string|Error} */exception) {

reason = exception;
}
} else {
error = new TypeError(
'`execute` exclusively can be invoked at a callable type.'
);
}
return [/** @type {null|string|Error} */reason ?? error, result];
}

Reflect.defineProperty(Function.prototype, 'execute', {
value: execute,
writable: true,
configurable: true,
});
Reflect.defineProperty((async function () {}).constructor.prototype, 'execute', {
value: executeAsync,
writable: true,
configurable: true,
});

And the minimum test case for this solution would be as simple and convenient as:

function proveSuccess(value) {
return value;
}
function proveFailure() {
throw new Error('failure');
}
async function proveSuccessAsync(value) {
return new Promise(resolve => setTimeout(resolve, 2000, value));
}
async function proveFailureAsync() {
return new Promise((_, reject) => setTimeout(reject, 2000, 'rejected'));
}
let reason, error, result;

([error, result] = proveSuccess.execute(null, 'successfully executed'));

console.log({ error, result });

([error, result] = proveFailure.execute());

console.log({ error, result });

([error, result] = await proveSuccessAsync.execute(null, 'async success'));

console.log({ error, result });

([reason, result] = await proveFailureAsync.execute());

console.log({ reason, result });

--

--

No responses yet