tj / co

The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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