co icon indicating copy to clipboard operation
co copied to clipboard

Proposal for an alternative to throw-catch error handling.

Open TEHEK opened this issue 9 years ago • 2 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? =]

TEHEK avatar Aug 26 '16 02:08 TEHEK

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.

freiit avatar Aug 26 '16 06:08 freiit

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

TEHEK avatar Aug 26 '16 17:08 TEHEK