Proposal for an alternative to throw-catch error handling.
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 [email protected]:
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 https://github.com/tj/co/issues/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 :)