Proposal for an alternative to throw-catch error handling.
TEHEK opened this issue · comments
Sometimes a proper error handling is important. The traditional try-catch error handling has the following "usability" issues:
- catches all errors, even those the author did not intent to catch:
try {
imadeatypo();
} catch (e) {
// if the error is silently suppressed because this snippet is never supposed to throw,
// you get to enjoy debugging:
// ReferenceError: imadeatypo is not defined
}
- kind of encourages disregard for error handling:
try {
doSomethingBig();
doSomethingBig2();
...
// way down below
catch (e) {
console.log('Eh... whatever... one of the two things failed');
}
- becomes too verbose when a more precise error handling is needed:
co(function* () {
let file;
try {
file = yield getFileFromCache('filename');
} catch (e) {
try {
file = yield getFileFromDisk('filename');
} catch (e) {
console.log('Unable to open a file');
}
}
let data;
try {
data = yield processFile(file);
} catch (e) {
console.log('Unable to process file');
} finally {
yield file.close();
}
console.log(data);
});
Now, what i propose is adding an additional method, say co.withResult
or co.wr
whatever. Instead of returning resolved value from yielded promises or throwing an exception if a yielded promise rejects, it will return a wrapper object, Result
.
A pseudo implementation for Result
largely insipred by Result in rust and Optional in java:
class Result {
constructor(value, isError, error) {
this.value = value;
this.isError = isError;
this.error = error;
}
isOk() {
return !this.isError;
}
get() {
if (this.isError) {
throw this.error;
}
return this.value;
}
or(altValue) {
if (this.isError) {
return altValue;
}
return this.value;
}
}
Returning that instead of the result directly allows us to rewrite our contrieved example as follows:
co.withResult(function*() {
let fileResult = yield getFileFromCache('filename');
// notice how errors can be handled immediately. In golang this is considered a good thing.
if (!fileResult.isOk()) {
fileResult = yield getFileFromDisk('filename');
if (!fileResult.isOk()) {
console.log('Unable to open a file');
return;
}
}
let file = fileResult.get();
dataResult = yield processFile(file);
console.log(dataResult.or('Unable to process file'));
});
and even if the error handling is not important, the resulting code can still be concise:
let file = (yield getFileFromCache('filename')).get(); //throws if anything is wrong
let something = (yield getFileFromDisk('something')).or(''); // either gets the result or returns the ''
yield writeToDisk(file + something);
});
What do you think? =]
Can't you just easily wrap that in an extra function?
let withResult = promise => new Promise(res => promise.then(result => res({ result }), err => res({ err }))
So if your original promise fails, you get back { err: 'whatever error...' } else { result: 'great result...' }.
let wR = yield withResult(getFileFromDisk('...'))
should make what you want.
\C
Am 26.08.2016 um 04:29 schrieb TEHEK Firefox notifications@github.com:
Sometimes a proper error handling is important. The traditional try-catch error handling has the following "usability" issues:
catches all errors, even those the author did not intent to catch:
try {
imadeatypo();
} catch (e) {
// if the error is silently suppressed because this snippet is never supposed to throw,
// you get to enjoy debugging:
// ReferenceError: imadeatypo is not defined
}
kind of encourages disregard for error handling:
try {
doSomethingBig();
doSomethingBig2();
...
// way down below
catch (e) {
console.log('Eh... whatever... one of the two things failed');
}
becomes too verbose when a more precise error handling is needed:
co(function* () {
let file;
try {
file = yield getFileFromCache('filename');
} catch (e) {
try {
file = yield getFileFromDisk('filename');
} catch (e) {
console.log('Unable to open a file');
}
}let data;
try {
data = yield processFile(file);
} catch (e) {
console.log('Unable to process file');
} finally {
yield file.close();
}console.log(data);
});
Now, what i propose is adding an additional method, say co.withResult or co.wr whatever. Instead of returning resolved value from yielded promises or throwing an exception if a yielded promise rejects, it will return a wrapper object, Result.A pseudo implementation for Result largely insipred by Result in rust https://doc.rust-lang.org/std/result/ and Optional in java https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html:
class Result {
constructor(value, isError, error) {
this.value = value;
this.isError = isError;
this.error = error;
}isOk() {
return !this.isError;
}get() {
if (this.isError) {
throw this.error;
}return this.value;
}
or(altValue) {
if (this.isError) {
return altValue;
}
return this.value;
}
}
Returning that instead of the result directly allows us to rewrite our contrieved example as follows:co.withResult(function*() {
let fileResult = yield getFileFromCache('filename');// notice how errors can be handled immediately. In golang this is considered a good thing.
if (!fileResult.isOk()) {
fileResult = yield getFileFromDisk('filename');
if (!fileResult.isOk()) {
console.log('Unable to open a file');
return;
}
}let file = fileResult.get();
dataResult = yield processFile(file);console.log(dataResult.or('Unable to process file'));
});
and even if the error handling is not important, the resulting code can still be concise:let file = (yield getFileFromCache('filename')).get(); //throws if anything is wrong
let something = (yield getFileFromDisk('something')).or(''); // either gets the result or returns the ''
yield writeToDisk(file + something);
});
What do you think? =]—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub #291, or mute the thread https://github.com/notifications/unsubscribe-auth/AH0MEUFNN01l6ACFbA069yMHV2zUyz3Jks5qjk-TgaJpZM4JtrvN.
Didn't think about that :) Kinda brilliant.
The only problem is that i'd need to keep that magic function in a separate module and keep a local convention, but otherwise, nice :)