Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for an alternative to throw-catch error handling. #291

Open
TEHEK opened this issue Aug 26, 2016 · 2 comments
Open

Proposal for an alternative to throw-catch error handling. #291

TEHEK opened this issue Aug 26, 2016 · 2 comments

Comments

@TEHEK
Copy link

TEHEK commented Aug 26, 2016

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? =]

@freiit
Copy link

freiit commented Aug 26, 2016

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.

@TEHEK
Copy link
Author

TEHEK commented Aug 26, 2016

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants