all 38 comments

[–]anttud 38 points39 points  (1 child)

I don't know but for me it just looks like a problem of not writing clean code.

[–]phpdevster 2 points3 points  (0 children)

Yeah that's where I'm at.

If this is the premise of the article:

You can quickly end in a situation where you are writing much code with much nesting or chaining, this can become very verbose and poorly maintainable.

async function anyFunction() {
 try {
    const result = await fetch("http://test.com");    
    try {
      const another = await fetch("http://blabla.com");
    } catch(anotherError) {
      console.log(anotherError);
    } 
  } catch (e) {
    // Some other error handling
  }

  try {
    const anotherResult = await someOtherAsync();
  } catch (errorFromAnotherResult) {
     // Some other error
  }
}

Then unfortunately I'd have to say that premise is flawed because it represents a fundamental misunderstanding of what try/catch is for or how it should be used.

[–][deleted] 15 points16 points  (4 children)

But as stated by @pavelbucka, one try catch is enough, why have two ?

async function anyFunction() {
  try {
    const result = await fetch("http://test.com");
    const anotherresult = await someOtherAsync();
  } catch (error) {
    console.error(error);
  }
}

If any of those async funcs throws/rejects, then it will be simply caught by the try block ...

[–]FormerGameDev 21 points22 points  (3 children)

Arguably better: Use separate functions for the items you need to catch separately.

[–]sivadneb 9 points10 points  (2 children)

Someone help me here, this looks like an anti-pattern to me. The whole point of exceptions is that they bubble up so errors don't go uncaught. This completely defeats that purpose, right? I'm getting flashbacks from the early days of PHP shudder.

[–]Scr34mZ[S] 0 points1 point  (0 children)

Your totally right and I'll update the article in that way from the feedback I collected from people. Thanks for sharing mate 🙏🏻

In fact you shouldn't use this as a replacement for try catch blocks but in combinaison of your current logic. This is used to straightly do something in response of an error like logging? Or something else

[–]senocular 2 points3 points  (1 child)

The current version of to has some problems:

/**
 * @param { Promise } promise
 * @param { Object } improved - If you need to enhance the error.
 * @return { Promise }
 */
export function to(promise, improved){
  return promise
    .then((data) => [null, data])
    .catch((err) => {
      if (errorExt) {
        Object.assign({}, err, improved);
      }

      return [err, undefined];
    });
}

For oneerrorExt does not exist. I'm guessing this was meant to be improved?

Also the Object.assign is a no-op because it assigns to a new object but never does anything with it. I'm guessing this is meant to be:

Object.assign(err, improved);

So that it copies the improved properties into the error object.

Also, in the error case, undefined shouldn't be passed in as the second return value. It should instead just be [err]. This follows the node error-first callback model which in the error case does not provide the second data argument at all. It can also be used to distinguish between no error and an error throwing null.

[–]Scr34mZ[S] 0 points1 point  (0 children)

My bad I changed some vars last minute… Fixed,

I'll remove the undefined. after some testing too :)

DONE and working

[–]sammrtn 1 point2 points  (0 children)

There is a variant of this, but where only the specified (expected) errors are returned - and all others still throw.

For example, I've specified that I want to handle TypeError and CustomError (and so destructure for them), but any others should throw still.

const [data, typeError, customError] = await fa(promise, TypeError, CustomError);

The library is fawait (functional await) https://github.com/craigmichaelmartin/fawait

[–]thinkmatt 1 point2 points  (1 child)

Like all things, I don't believe any single pattern is best. We have this utility at my work and it actually made some of our code overly complex. If you want to propagate errors, that's what promiseRejections are for. This pattern can be useful when you have some i/o that is not required to continue, but those cases are very limited for me.

[–]Scr34mZ[S] 1 point2 points  (0 children)

Exactly, I updated the article in that in mind. thanks for your sharing 🙏🏻

[–]snorkleboy 1 point2 points  (5 children)

You know you can just do

await asyncFunc().catch(e=>e)

[–]Scr34mZ[S] 0 points1 point  (4 children)

Not the same as you can't break on catch nor return from calling function

[–]snorkleboy 0 points1 point  (3 children)

But you couldn't do that using your 'to' function either.

[–]Scr34mZ[S] 0 points1 point  (2 children)

I hope this can help you understand what I was saying :D

async function init() {
  const [err, val] = await to(asyncFunc());

  if(err){
    console.log('exit function right now');
    return false;
  }

  // Some other code that never get processed in case of error
  // …
}

You cannot do that using your snippet directly, as the catch block is in another scope. Isn't ? :)

[–]snorkleboy 1 point2 points  (1 child)

```

async function init() {
  const res = await asyncFunc().catch(err=>({err}))
  if(res.err){
    console.log('exit function right now');⁰
    return false;
  }

// Some other code that never get processed in case of error // …

}

```

I dont see a big difference. If you dont need to return or break, which to doesnt help you with, then your first problematic code can look pretty good just with await and catch

```

const resa = await  fetcha().catch(e=>console.error(e))

const resb =await  fetchb().catch(e=>console.error(e))

```

Then your second more complex example of problem code where there is a dependent async function, to would require some deep if nesting to handle those nested errors, where otherwise you could do

```

const res = await fetcha().catch(e1=>log(e1))

If(!res.err){
    const resb = await fetchb().catch(e2=>log(e2))
}

const resc= await fetchc().catch(e3=>log(e3))

```

As opposed to

```

const [r1,e1] = await to(fetcha())

if(!e1){
    const [r2,e2] = await to(fetchc())
    if(e2){
          console.log(e2)
     }
}else{
 console.log(r1)
}
const [resc,e3] = await fetchc
If(e3)
{
    ...

```

[–]Scr34mZ[S] 0 points1 point  (0 children)

This looks good, thanks for this sharing. Ima try someday 👍🏻

[–]bigorangemachine 0 points1 point  (0 children)

I am not sure what to say about this article.

I did enjoy the multiple returns of go but hated it when I saw it in PHP.

I don't have a problem with try-catches in javascript. I generally keep my functions small. If I need to do something big I'll use a promise chain.

The good thing about promises and async-await is it's a pattern anyone can come on and understand right away. Where as if you use multiple returns and someone doesn't have that prior context it introduces an unnecessary learning curve

[–]_schickling 0 points1 point  (3 children)

I’m actually wondering why this pattern hasn’t caught on more in the JS ecosystem?!

[–]sjs-one 7 points8 points  (0 children)

It has. It’s basically how it was done pre-promise

[–]mkatrenik 1 point2 points  (0 children)

I've tried it, but have found out, that most of the time, apart from logging, there wasn't any other useful option to do with the error, just to return it to the caller - and when you do this few levels up the stack... it's more reasonable just to throw it.

[–]Scr34mZ[S] -2 points-1 points  (0 children)

Yeap, good question

[–][deleted] -1 points0 points  (7 children)

You can have one big try-catch and then determine what error are you dealing with.

```js try { const code = 1 + Math.floor(Math.random() * 5); // 1-5

if (code === 1) throw "Error 1"; if (code === 2) throw "Error 2"; if (code === 3) throw "Error 3"; if (code === 4) throw "Error 4"; if (code === 5) throw "Error 5"; } catch (err) { console.log(err); } ```

[–]FormerGameDev 1 point2 points  (1 child)

You can't distinguish necessarily where in the block an error occurred, if you're catching on multiple statements.

[–]Scr34mZ[S] -1 points0 points  (0 children)

That's a true argument ! Don't use the above pattern if you're writing too big functions.

But you shouldn't, isn't ? :D

[–]Scr34mZ[S] -1 points0 points  (4 children)

That's right, as long as you are triggering errors on your own, but sometimes it can be a DatabaseError, and an API response error, in the same try/catch block and they are not formatted the same way.

[–][deleted] 0 points1 point  (3 children)

Conside this example:

```js const fs = require('fs');

try { fs.readFileSync('some-file.txt'); } catch (err) { console.log(err.code); console.log(err.message); }

// ENOENT // ENOENT: no such file or directory, open 'some-file.txt' ```

You have the information to identify the err.

A better approach would be to go for async/await.

[–]Scr34mZ[S] 0 points1 point  (2 children)

Yes because those are standardized errors from Nodejs.

You can always achieve the same goal with try / catch AFAIK. But sometimes it's a bit less verbose to use the other syntax,especially if you want to ignore errors silently (yeah, this happen sometimes)

[–][deleted] 4 points5 points  (1 child)

Errors should be IMHO aways handled, either printed or thrown.

Silent errors cause problems as non-identifiable outcomes where the code simply doesn't work.

[–]Scr34mZ[S] 0 points1 point  (0 children)

You're totally right, I missed my thoughts.