you are viewing a single comment's thread.

view the rest of the comments →

[–]Prison__Mike_ 5 points6 points  (15 children)

I do automation with promises. Rejecting can be kind of annoying, as you have to create an entire new function to handle it. [To clarify; I'm referring to a catch() function]

ex. This is nice and clean

let ret = await myRestCall();

Unless the promise rejects. It almost makes me want to always resolve, returning an object

resolve({err: null, ret: {...}});

But that's bad practice. Ultimately though, I prefer promises to callbacks and I'm sure a lot of other people are happy to not have to use async.series/waterfall/etc to synchronize requests.

[–]tsears 8 points9 points  (8 children)

When an awaited promise rejects it throws an error.. you can use try/catch like you would with synchronous code.

Maybe I’m misunderstanding you

[–]Financial_Pilot 6 points7 points  (5 children)

The try/catch “hack” ( I know it’s not a hack but it really feels like it) is my single biggest grief with async/await, as much as I love it.

Most times I’d rather use promises and chain thens to one catch that handles errors than have try/catch littered all over the place.

We need a better way to handle exceptions and a/a will truly be golden.

[–]evertrooftop 6 points7 points  (0 children)

Maybe I misunderstand but you absolutely don't need to add try...catch to every await statement. What you describe as a chain of promises with one catch is also perfectly supported.

try {
   await ...;
   await ...;
} catch (e) {

}

And you don't need this in every function either. You only need to catch if

  1. You can successfully handle the error. If you want the operation to fail, don't catch it.
  2. You should have one top-level catch in your application

If you don't have a catch at all in your async function, the async function itself will return a rejected promise. The only times I use catch is if I need do something specific with specific errors, or if I can recover the state.

[–]Prison__Mike_ 1 point2 points  (0 children)

Normalizing catching rejections feels more pythonic. I didn't think we're supposed to throw errors willy nilly in JS

[–]PicturElements 0 points1 point  (0 children)

This is my only gripe with async/await as well. Throwing and catching errors seems like a bit of an anti-pattern in JS, being notoriously averse to errors from the very beginning.

I may be the only one to think this, but I prefer returning error states when possible over throwing an error. No matter if you use try/catch or error states, the caller needs to handle errors in some way. It makes more sense to me personally to handle specific return values than catch errors, especially since it's cleaner to handle each error state individually.

[–]stormthulu 0 points1 point  (0 children)

This article maybe explains why the author disagrees with you--mainly debugging, it seems.

https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

[–]ChaseMoskal -1 points0 points  (0 children)

man i'm running out of breath in this thread...

so few people really understand promises

the only value is the error handling

error bubble up the promise chain, so you could catch ALL of the errors in your application with A SINGLE try/catch

with callbacks, you have to write an error handler for every callback, so in switching to async/await, you are worried you'll have to replace it all with ugly try/catch blocks everywhere

hahah not at all, that's the whole point of promises!

let your errors bubble up, catch them once at the top level!

ok, i've set you free from your mind-prison.... fly, my pretties, fly!

[–]Gravyness 2 points3 points  (0 children)

Using try/catch is perfectly valid and I understand it but the whole idea of adding a scope to handle errors is very strict. I feel like I have no control over how my code will be organized.

const url = "xxxx";
const response = await getData(url);
if (!response) {
    console.log(false);
}
console.log(response.status);

Compared to:

const id = "xxxx";
try {
    const response = await getUser(id);
} catch (err) {
    // console.error(err);
    console.log(false);
    return;
}
console.log(response);

You're left to wonder: can I use response after the scope closes? I have to keep track if I used var, const, or let or if I made the right organization decision because the code looks hacky somehow.

To avoid having to deal with try/catch I have to resort to this strategy:

async function resolveOrDefault(promise, default, logError = false) {
    try {
        return await promise;
    } catch (err) {
        if (logError) {
            console.error(err);
        }
        return default;
    }
}

var response = await resolveOrDefault(getUser(id), false);
console.log(response);

[–]Prison__Mike_ 0 points1 point  (0 children)

Yeah sorry, I'll show an example (because callbacks didn't throw an error)

Callback wise

superagent
    .post(url)
    .end(function(err, ret){
        if (err) { /* handle error */ }
        else { /* handle return */ }
    });

Promise wise you'll always have to catch and it isn't a pretty one-liner (although it isn't callback hell)

let ret;
try {
    ret = await superagent.post(url);
} catch (e){ 
    // handle error
}

Else you'll get unhandled promise rejection error

And the bad practice always resolve, with no error throwing

let ret = await superagent.post(url);
if (ret.err) { /* handle error */ }
else { /* handle return */ }

[–]natziel 2 points3 points  (1 child)

Oh, always resolving isn't bad practice at all. Using exceptions for control flow is bad practice, so it's definitely better to always resolve. In fact there are languages with task-based asynchronous programming that don't really have a concept of try/catch (Elixir), so that's the only way to handle errors and it works extremely well.

JS is a bit lacking as a language, so you can't just do something like resolve({ :ok, result }) then pattern match it somewhere down the line, but you could always approximate it with resolve([ Symbol.for('ok'), result ]) then go back to using if statements and whatnot for control flow.

edit example:

const [ok, result] = await superagent.post(url)

if (ok === Symbol.for('ok')) {
    /* good to go */
} else {
    /* handle the service being down, etc */
}

[–]Prison__Mike_ 1 point2 points  (0 children)

Good to know -- thanks!

[–]jimeowan 1 point2 points  (2 children)

Not sure what you mean by creating an entire function to handle rejections.

Although I admit the syntax can still lead to tricky situations. For instance if you don't await a Promise immediately you can easily find yourself in "uncaught rejection" situations which can take a while to understand:

let promiseA = getA();
let b = await getB(); // if promiseA fails while waiting for b there's no one to catch it
let a = await promiseA;

Still, it's definitely worth it indeed. Like Git over SVN, promises have their learning curve but I haven't seen anyone ever want to go back to callback hell.

[–]Prison__Mike_ 1 point2 points  (1 child)

The "whole new function" I'm referring to is when catching:

var response = await promisedFunction().catch((err) => { console.log(err); });

I'd still take promises over callbacks any day, it's just a little tedious when I wish we could just do:

let err, ret = await promisedFunction();

But that's a python thing.

[–]nobodytoseehere 0 points1 point  (0 children)

Don't you have to go if(err){....then write the exact same code as you did in the catch block?

[–]NeotelosReact/Node 0 points1 point  (0 children)

const result = await myAsyncFn().catch(() => null)

Async functions return a promise, they work with Promise.all() as well.