JavaScript async/await implemented in V8
chromium.googlesource.com
chromium.googlesource.com
Getting data is as easy as:
async function main () {
try {
const res = await fetch('https://api.github.com/orgs/facebook');
const json = await res.json();
console.log(json);
} catch (e) {
// handle error
}
}
So I am quite happy when this lands in modern browsers asap. const json = await (await fetch(https://api.github.com/orgs/facebook')).json();
or do I have to name it?EDIT: I missed the `.json()` part, but I suppose it doesn't return a Promise, no?
The fetch function can for example also be used to get images. There you would use .blob() and assign that to an image.src with URL.createObjectURL(blob);
See here: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/U...
const json = await fetch('https://api.github.com/orgs/facebook').then(res => res.json());EDIT: got it, you want to do it inside an aync function with some other code after the await, then I think it makes sense in that case.
const responsePromise = fetch(https://api.github.com/orgs/facebook');
const response = await responsePromise;
const jsonPromise = response.json();
const json = await jsonPromise;It should be said that fetch() is quite low level call compared to what we've had before regarding ajax, so it makes sense in larger projects to wrap it to keep any logging, error handling and retry in one place.
Using MIME types isn't hand holding or making an assumption.
Yes, people who need the low level features will wrap it with a series of competing high level libraries. It's a missed opportunity to make a standard, capable high level API though.
You can do it, it's not insane, but native async/await will be much, much nicer.
There were also cases where I wasn't sure if babel-watch was reloading dependencies properly if I used async code in a file, which is why I had to keep switching to babel-node or running the code through babel and inspecting the generated code to debug. Overall, it just felt very new and unpolished (which I guess it was...I was working with it around March, so babel-watch was < 1 month old and babel async/await was about 2-3 months at the time) to be relying on all the time.
const should be the default, and if you're going to reassign it, then use let.
In most code, you'll use const on the vast majority of variables, and it'll make let assignments stick out, which helps a lot when you're browsing the code or refactoring.
const val = 'a'; val = 'b'; // TypeError
const obj = { val: 'a' }; obj.val = 'b'; // This line will still work.
obj = { val: 'b' }; // TypeError
There's also `Object.defineProperty()`, `Object.freeze()` for more control over mutability.
This is why I'm hoping to see native JS immutable data structures eventually (maybe it's already in a proposal somewhere?). ImmutableJS and Mori are great, but having a native solution that's available everywhere would be ideal.
That said, `const` is picking up to be the default for everything. I can definitely see good reasons to use it when declaring functions and required modules or working in a team.
One case that surprised me a bit is that it's correct to use const in for-of loop, e.g.
for (const a of arr) {}
`const fn = (i) => ...`
1. Admittedly my only data point is AirBnB's style guide / ESLint config: https://github.com/airbnb/javascript#references--prefer-cons...
function main() {
try {
const res = fetch('https://api.github.com/orgs/facebook'); // yield here
const json = res.json(); // yield here
console.log(json);
} catch (e) {
// handle error
}
}
and the runtime would automatically yield this coroutine and let other coroutines run whenever some call blocks.I guess the only realy difference would be that I would make `await` (and `async`) implicit, similar to how exceptions are handled implicitly in the above snippet (i.e. we don't annotate functions as `throwing` and we don't use an `attempt` statement (analogous to `await`) when calling `throwing` functions).
Edit: Reading various articles, the best explanation is that since the single-threaded nature of JavaScript makes synchronisity implicit (i.e. all code is run in an implicit transaction), it has to make asynchronisity explicit. The alternative would be to have coroutines/fibres (implicit asynchronisity), but with explicit `atomic` blocks (for synchronisity).
C# doesn't provide any synchronisity guarantees, so I guess the motivations there were different (it's really hard (impossible?) to implement fibres efficiently, and even harder to allow for native code interoperability).
Is it possible to have a yield inside a called function (i.e., not the generator function itself)? Last time I checked this was not allowed (?)
Anyway, if so, I would strongly prefer this over a async/await construct, which is less general.
Call a generator function from a generator function? Sure, you just need to transitively yield its content using `yield* [[expr]]` (which delegates part of the iteration to the `expr` generator)
const res = fetch('https://api.github.com/orgs/facebook'); // yield here
actually requires a "yield" in front of the "fetch"? But what if I want the fetch() function to decide whether to yield or not?Of course, fetch(), could yield a status specifying whether its calling-ancestors should yield, but this can become unwieldy very quickly, and might require an exception mechanism of its own. Better to just let called functions yield.
> But what if I want the fetch() function to decide whether to yield or not?
In javascript? The question doesn't really make sense, a function is either sync or async.
Well, either fetch() blocks, or not. If it blocks, then it should yield, its caller should yield, etc. If it doesn't block, then it can just run to completion.
> Well, either fetch() blocks, or not.
Javascript functions always block.
> If it blocks, then it should yield, its caller should yield, etc. If it doesn't block, then it can just run to completion.
If a function is async, you must yield[0]. It may not need* that capability (conditionally) and doesn't have to yield at all e.g.
function*() {
return 4;
}
is valid and will not suspend. You must still yield*[0] on it so that it is properly resolved as a generator.[0] I think that's not an issue for async/await as it's syntactic sugar for then chains, the code itself may be converted to a generator or whatever but it will embed the driver
1) Yielding is decoupled from the asynchronous action itself, giving the caller fine-grained control when to yield. For example, with async/await, management of parallel operations is straight-forward:
var promise = fetch('https://foo.com/');
// ... do something useful while the fetch runs ...
var result = yield promise;
Same goes for concurrent fetches: var promise1 = fetch('https://foo.com/');
var promise2 = fetch('https://bar.com/');
[result1, result2] = yield Promise.all(promise1, promise2 );
2) Promises + async/await allow you to switch back-and-forth very easily between callback-style and coroutine-style APIs, which makes integrating old APIs very easy: async function f() {
var p = fetch('https://foo.com/');
p = p.then(function(inbetweenResult) {return new Promise(function(resolve) {
oldapi.onresult = resolve;
oldapi.process(inbetweenResult);
})});
return await p;
}
f().then(...)
... and so on.*certain DOM APIs due violate this contract, notably window.open() on firefox pauses everything but setTimeout.
That being said you can totally have globals and whatnot that can be mutated anywhere (e.g. parameters passed to a function or literal globals installed in the global scope) or things declared at a lower scope that can still be modified by multiple functions.
Hope that clears it up
(Although, just to be pedantic, the concept of futures is way older than F#. Alice's syntax, for example, is pretty close)
async/await is the final piece in the puzzle of providing real solutions to callback hell in JavaScript.
I personally don't know many devs who would choose to learn a new lang because of a feature (assuming that they didn't want to learn it before it existed)
I came from a language that can do this kind of thing implicitly using coroutines (i.e., I could write imperative code that would pause on a network request and resume afterwards). Have to wade through callback hell -- even with Promises -- was a serious step down.
With async/await being explicit, combined with functionality like Promise.all(), this is actually a better solution than I had before, because I can spawn a dozen queries at once and wait for them all to complete before I continue. I've used that to good advantage already once with an await Promise.all(...) call, and the patterns are so easy to follow that it brings tears to my eyes.
Or I can inject more traditional callbacks when the logic would be easier (or when it would allow several simultaneous calls to complete), which does happen from time to time.
As to whether I'd learn a new language because of it: I'm considering learning Elixir because of even stronger guarantees [1], actually, so yes, I would.
[1] Apparently in addition to being able to write code using light threads like this, you can also put a cap on how long the VM will run code, so if some idiot developer puts a loop in the code that doesn't return control enough, instead of destroying your UI experience it will just pause that loop and resume the main loop. Still have yet to try it though.
Enough with the FUD around .NET/C#, I'm a full time Linux greybeard and even I'm sick of it.
As for creating classes so often, they are adding better support for tuples in the next version (c# 7).
https://www.twilio.com/blog/2015/10/asyncawait-the-hero-java...
Here's another awesome article on it:
http://amasad.me/2015/10/31/javascript-async-functions-for-e...
So `await (x)` is ambiguous. Making the outer function `async function () {...}` makes await a keyword inside that function, allowing them to move forward with that syntax in a non-breaking way as the parser now knows that there can't be a function named `await` inside any async functions.
A simple example would be the concurrent fetch of two urls.
Solid reasoning, in my opinion.
http://kangax.github.io/compat-table/esnext/#test-async_func...
Apparently Microsoft Edge seems to have been the first browser to implement it... good job Microsoft!
This feels like a step toward added confusion rather than language unity. Much like the majority of of ES6 feels to me: bolted-on features from popular synchronous languages that themselves are only now adding features like lambdas.
I don't want to write faux-event-driven code that hides the eventedness beneath native abstractions. And I definitely don't want to work with developers, new and old, trying to learn the language and whether/when they should use async/await, promises, or fall back to an es5 library or on* event handlers. I want developers who grok functional event-driven code with contextual closures.
How about:
async function main () {
try {
const responses = await Promise.all([
fetch('https://api.github.com/orgs/facebook'),
fetch('https://api.github.com/orgs/facebook')
]);
const jsons = await Promise.all(responses.map(res => res.json()))
} catch (err) {
console.log(err)
}
}
I think this is pretty clear and not needing any library for this is quite nice. async function main () {
try {
const jsons = await Promise.all([
fetch('https://api.github.com/orgs/facebook'),
fetch('https://api.github.com/orgs/facebook')
]).map(promise => promise.then(res => res.json()));
} catch (err) {
console.log(err)
}
}
This way, if one response was much quicker than the other, you could begin sending it through the `res.json()` portion immediately, instead of waiting for both responses to return before continuing.IIRC Promise.all will run the two responses in parallel, but will not send the first into the map before both are finished.
Which is why I'm a massive fan of Futures from ramda-fantasy[0] instead of Promises for certain tasks!
[0] https://github.com/ramda/ramda-fantasy/blob/master/docs/Futu...
Yeah, I know that. I don't mean .all schedules them to run (e.g. like doing thread.start()), of course they auto start.
By run I mean that inside the .all implementation syncs with you about their completion ("tells you when all of them are settled").
>It depends on your Promises whether they are doing something outside the single threaded event loop or not for them to be parallel or not.
That's not the point though, which was the parent's assertion about whether the first to resolve will escape .all and go into the .map, which I don't think is the case.
async function main () {
try {
const jsons = await Promise.all([
fetch('https://api.github.com/orgs/facebook'),
fetch('https://api.github.com/orgs/facebook')
].map(promise => promise.then(res => res.json())));
} catch (err) {
console.log(err)
}
}
As written you get "Promise.all(...).map is not a function" async.parallel({
facebook: done => request("https://api.github.com/orgs/facebook", done),
twitter: done => request("https://api.github.com/orgs/twitter", done)
}, (err, responses) => console.log(err, responses); );
And output something like: null, {
facebook: { // facebook data },
twitter: { // twitter data }
}
Sure it suffers from a third party dependency and I would argue its slightly less concise, but we're getting to the point of opinionated API's just like some developers prefer promises, some will prefer async/await, others will continue with vanilla callbacks.In the same vein async.js will be catching all errors passed to done(err, response) and in the case of async.series() and similar calls, will stop the execution of concurrent actions as at the first error that is caught.
Therefore I think its completely valid, because even though our examples are different, to most developers it doesn't even matter.
I think you have a completely valid point, and it's probably a better world where we have proper error propagation and composition, just the reality of the situation is what it is.
Nobody who realized the fragility of async/callbacks and understanding what it takes with them to make at least a passably robust program would stay with async, they would quickly switch to promises/fibers/equivalent or stop using node. Although I have seen projects on github that use try-catch blocks properly with callbacks/async and I have to wonder what made them think that there isn't a better way.
And again, async does not catch any errors, it only forwards error callbacks based on some very loose convention which isn't even honored in core node apis. Just because most node (or rather callback/async) users are clueless about error handling doesn't make the examples equivalent.
There is, of course, no library needed to use pure promises for the same functionality, though.
My point is that all of this is possible without async/await, which to me are abstractions that cloud the landscape, obscuring the real evented nature of JavaScript that makes async programming so easy, IMO.
async/await looks nice but doesn't scale. As soon as you have to do something other than wait for a callback it falls apart.
Waiting for a callback is what you do 90% of time. For the rest, you can always combine async/wait with Promises...
The think I don't like about promises its that you need to write a lot of lines that you could avoid with async/await, i.e:
new Promise(function(success, failure){
db.fetch('sql...', function(err, data){
if (err) failure(err)
else success(data)
})
})
VS var data = await db.fetch('sql...')
Also if you concatenate with then promise.then(function(done, args){
try {
db.fetch('sql...', done)
} catch(e) {}
})
.then(function(done, args){
try {
db.fetch('sql2... {args}', done)
} catch(e) {}
})
.then....
VS try {
var data1 = await db.fetch('sql1...')
var data2 = await db.fetch('sql2... {data1}')
} catch(e) {}
btw its just pseudocode but you get the main ideaI'm also in the boat of confused people of why is async-await requested by a good portion of the js community - wrapping the block within the function in a try-catch seems like a bad idea, a brute force mechanism. IMO this is language bloat, and does not really solve problems we have while introducing a more expensive vector for managing flow (try-catches).
One missed point about the .catch with promises is that it allows you to branch flow discretely - it is not a perfect solution for branching flow (it is awkward when branching due to more than just a binary outcome), but with await, one might end up writing repetitive code for certain flows where one expects common calls to be made downstream in async data fetching flows.
Depends whether you can survive and continue from an exception inside the .then to the next step or not.
e.g, naive example:
.then(() => {
var foo;
try {
foo = getFoo();
} catch (err) {
foo = 10; //some default value
}
return foo;
}).then( (foo) =>
etc.So you might not want to reject automatically inside then if getFoo raises, but recover and return a default value.
.then(getFoo)
.catch(err => 10) //some default value
.then( (foo) =>It's been said that "callbacks are imperative; promises are functional". It's true. Furthermore, callbacks structure control flow and promises structure data flow. Instruction scheduling is an explicit sequence with callbacks (well, assuming the API calls you back precisely once), but scheduling is an implicit topological sort of the directed acyclic dependency graph of promises.
Sometimes, when it comes to side effects, implicit scheduling is less than ideal. You need specific things to happen in a specific order. The solution is to introduce data dependencies to force a particular schedule. This is precisely what is done by monads in Haskell. However, unlike Haskell, JavaScript doesn't have "do" syntax, so there's no convenient notation for a nested chain of bindings.
The result of not having monadic binding syntax is that you wind up with some funky nested chain of getY(x).then(y => y.then(z => z.then(.... OR you have to declare variables up top, flatten your then blocks in to a promise chain, and often ignore intermediate values:
let y, z;
getY(x)
.then(returnedY => { y = returnedY; return zPromise(); )
.then(returnedZ => { z = returnedZ; return ......; }
.then(......another side effect.....)
.then(_ => f(y, z))
async/await neatly eliminates this problem by reusing the traditional coupling of data flow dependencies with first-order control flow dependencies.Practically:
let y = await getY(x);
let z = await getZ(y);
......another side effect....
return f(y, z)Do you have any good escapes l examples of further reading on this? I'm not sure I agree with this statement, or perhaps don't fully understand it. It seems like both cases boil down to functions as data (function pointers essentially) that are called at the end of some event or chain of events.
To me, they seem to be the exact same thing but worth promises being much more human readable.
readThisFile: string -> (string -> void) -> void
Usage: readThisFile('foo.txt', result => ...)
You have a double void here. Which is the hand-wavy justification as to why callbacks don't compose. On the other hand, a promise's value is Promise<actualTypeOfValue>, which you can pass around as data without asking the other end what it wants to do with the value (aka decoupling). You do have the imperative action at the top to kick the whole chain into motion, but the intermediate stuff are most of the time side-effect-free.Hopefully that was clear enough to see that passing actions around is akin to controlling the flow with e.g. if-else, and that passing data around is declaring how your tubes/rail tracks/whatever analogy for monads are constructed, aka data flow.
getY(x)
.then(returnedY => Promise.all([Promise.resolve(y), zPromise()])
.then([y, z] => { ...another side effect...; return Promise.all([Promise.resolve(y), Promise.resolve(z), sideEffectPromise()])
.then([y, z, _] => f(y, z));
Not the cleanest, but you preserve the isolation without relying on polluted variables bleeding into function scope. In addition, one can do proper error handling via .catch as opposed to the brute force unnecessary catchall that is a try-catch.2) catch's entire purpose is to be a catch-all for unexpected errors. For expected errors, it's better to have explicit error values. Bluebird.js at least lets you differentiate .catch from .error, where .error handles the explicit error values generated from traditional Node.js callbacks.
>> functional event-driven code with contextual closures
This paradigm works very well on the server side too. Ex: A HTTP requests makes a query to a database server, then returns the result to the browser
This gives parallelism without the complexity of threads.
Being simple doesn't make them optimal.
Promises are a clutch for lack of a built-in language feature, and async/await is that.
They add extra visual clutter, they can eat exceptions [1], they break the intuitive flow of code, etc.
[1] Yes, only if they are used badly. That's kind of the point. C is an excellent language too if everybody never uses it badly. The thing is people do, and a feature/language that doesn't need a corner case or mental model to keep in mind is better than one that does in that regard.
It absolutely does make sense. How many "class builders" have been written in the JS community since ES6 got classes ? That's right. Now you don't have to rely on co/thunk and what not to write readable async logic. Promises are still callbacks wrapped in objects. Now the consumer of a function that returns a promise doesn't have to deal directly with the promise anymore.
This feature is something JS should have had a long time ago, no question.
> This feels like a step toward added confusion rather than language unity.
Things like prototypical inheritance are the biggest source of confusion, along with "this" behavior.
> beneath native abstractions
The correct terminology here would be syntactic sugar.
Look you might not like this, but what is better ? community fragmentation with transpilers and what not, or a single language that answers most of the needs of the community ? libraries that handle complex abstractions or documented syntax that allows getting rid of these poorly documented and poorly maintained libraries? I don't want to use generators to mimic corroutines and depend on co/thunkify to write readable async code. I don't want to use 3rd party language X or Z that will get me the syntax I want, I want everybody to use the same language without obscure patterns.
In the end, as you pointed out, these construct may work against developers in large/concurrent codebases and is an undoing of the simplicity of Javascript, one of its original strength. On the other hand we can't really say this was not coming, Javascript being a single target language with many stakeholders around it.
Maybe what's needed is to stop teaching object-oriented/imperative programming as the main paradigm. But well before that happens, hopefully WebAssembly will create a evolutionary market for languages vs. a language by committee.
C++0x started adding the kitchen sink and that's when C++ jumped the shark. JS seems to be headed in the same direction...
This is speaking from personal experience, albeit not in JS, but rather .NET. However, the story there is similar - .NET used callback-based continuations first (the Begin... pattern), then added promises (Tasks) in .NET 4.0, then finally async/await syntactic sugar in C# 5. We introduced tasks to our codebase pretty soon, but had to avoid await for a while because of the requirement for the code to be compilable with C# 4. When we finally dropped that requirement a couple of years later, and started moving to await, code became much simpler and cleaner as a result, and new code is also much easier to write, with fewer silly mistakes (like "I forgot to check for errors").
I'd love to hear about people who have used it in large and complex projects, from a debugging standpoint.
As of now, using Bluebird (with its source in a different, blackboxed script), it is possible to follow the code execution through the event loop with async debugging, in a very elegant and enjoyable fashion.
I find async/await much more appealing when coding, but I'm worried about quality of life when hardcore debugging, as in my current project it can make me waste hours at a time when something if completely fringe happens.
There are 2 ways to transpile async-await.
a) Default, with regenerator, transpiles it to ES5
b) If your env supports generators (like Node, or newer browsers), you can use async-to-generator plugin.
Regenerator gives you unreadable code, but it will run everywhere. async-to-generator gives you (relatively) readable code.
Source maps support has improved a lot, and you can choose to see only your original code while debugging. You'd be setting breakpoints on your code instead of transpiled code. So you should be fine whether you're using regenerator or async-to-generator. There might be corner cases (very rare) depending on your env; in which case if you're using regenerator you might need to switch to async-to-generator to find the bug.
Source maps work well with node-inspector. If you'd like to see better stack traces in say "mocha" tests (or other test framework), use https://github.com/evanw/node-source-map-support.
Overall, the developer experience is now pretty good. Thanks to all the work that's gone into the tooling.
Having said that I have not seen worse stack traces with 'async/await. I think they are similar.
I wonder if there is any babel/webpack gurus out there can make it work ?
Oh btw, I recommend windows users to try microsoft edge for debugging and runtime inspection, it is so slick :)
Well, it claims to be a prototype. Can anyone from the team comment?
Quite exciting in any case!
// Callback
dataCollection.find('somethingSpecific', function getIds(dataArray) {
var ids = dataArray.map(item => item.id));
display(ids)
});
// Promise
var dataArray = dataCollection.find('somethingSpecific');
var ids = pmap(dataArray, function(item) { // Cancer cell
return item.id;
});
pdisplay(ids); // Cancer cell
// The cancer grows ...
function pmap (dataPromise, fn) {
return dataPromise.then(
function(data) {
return map(data, fn);
});
}
// The cancer grows ...
function pdisplay(dataPromise) {
dataPromise.then(function(data) {
display(data);
},function(err) {
display(err);
});
}Dont know why people love so much async/await. In the end of the day, this all (in node land, for instance) will be just a function call in the libuv, this will never change, this is because the pattern is really good.. Why overcomplicate that?
OTOH, I can't complain about removing dependencies like "when" or "bluebird", and it'll be nice to not have to simulate promise returns in testing.
Like, what's the equivalent of Promise.all with async/await? And how do tou do stuff synchronously after kicking off an async process?
If you want to use async/await with something that's asynchronous but not Promise based, you'll first need to convert it into a promise before you can async/await it. This is actually very elegant in my opinion: instead of proposing yet another way to do asynchronicity in JS, async/await fully embraces Promises, which you already know.
Of course, this means that async/await also has all of Promise's downsides. For example, you can only resolve a promise once, so neither Promises now async/await make dealing with streams of asynchonous events any easier.
https://github.com/caolan/async
May be a replacement until the async is implemented in V8
Or, short ver: less indentation.
Not without Fibers. Javascript doesn't have Fibers though there an implementation in Nodejs.
What it changes is that you don't have to rely on yet another library anymore to get the behavior people mostly use yield for. I'm talking about co/thunk . While it doesn't solve the red/blue function issue [1], it makes writing async code a bit less tedious.
At the end of the day it will force everybody to return promises from async functions, rather than requiring callbacks,which is a good thing.
[1] : http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
coroutines are used to make your code appear to execute without a need for a callback, and in order to create a coroutine one must wrap the function doing the awaiting in a generator.
now, I am assuming (perhaps incorrectly) that the async keyoword marks the function for wrapping, and await lets you know where the yields should go. or something like that :)
of course, now thats its all native, I have no idea any more
/*
The following code may or may not use an await.
Not await-ing would be better user experience.
*/
async function sendEmails(id) { ... }
async function signUp(userData) {
const user = await db.saveUser(userData);
sendEmails(user.id); // <-- Not awaiting send to complete
return "Congrats. Signup complete!"
}
/*
On the other hand this code needs an await:
*/
async function updateGlobalState(obj) { ... }
async function doSomething(data) {
await updateGlobalState(data); //await needed
return global.x;
}(The exact point where this gets resolved, relative to other pending callbacks/resolutions, is a very wonky detail involving constructs not exposed at the language level, which varies from engine to engine in spite of what the standard says: https://jakearchibald.com/2015/tasks-microtasks-queues-and-s...)
Whenever the call resolves, even if the send does encounter errors, this function or its caller aren't going to know about it either way. That's why this kind of promise-abandonment is pretty bad design - I wouldn't be surprised if there's already a draft in the works for some kind of "use strict" option that causes warnings / crashes when synchronous code finishes with Promises unreferenced (or at least something in tooling / profiling to trace 'promise leaks').
Not backwards-compatibly. You'd need to make every function async and every value a future/promise (semantically at least, then optimise most cases back to sync somehow, which incidentally means possible behavioural changes based on what the optimiser manages or doesn't manage to syncify).
try {
await thing1()
} catch (e) {
console.log(e)
}
try {
await thing2()
} catch (e) {
console.log(e)
}
// ... and so forth ...
That's still nothing like the pyramids you get in callback world. async function doTheThing() {
await thing1()
await thing2()
}
That just works, if there's an error in thing1(), doTheThing() stops at that line and returns the rejected promise to whatever called it. The callback world is clearly the more complex here, where all error handling has to be properly wired and there is no default handling. The node callback equivalent to the above is: function doTheThing(callback) {
thing1(function (error, value) {
if (error) callback(error, null)
thing2(function (error2, value2) {
if (error2) callback(error2, null)
callback(null, true)
})
})
}
You can't forget either if (error) check or else errors will just silently be eaten/ignored.The "implicit behavior" introduced by async/await are essentially the same things you are used to in code without async/away, such as the way try { } catch { } and throw naturally work in synchronous. That alone should be considered a simplifying improvement.
So, the equivalent to that code with callbacks is the one from your previous example and not the simplified one that doesn't handle errors. Not forgetting "if (error)" in this case will be exactly the same, as not forgetting "catch (error)" or "if (error)" if you handle errors properly in the first place. But these are just patterns and with enough consistency they don't introduce much cognitive load. What does is implicit behavior and you don't have one with callbacks in your example, but you do with await. It's not as bad, as with threads, but still a little bit worse, than with callbacks.
«So, the equivalent to that code with callbacks is the one from your previous example and not the simplified one that doesn't handle errors.»
No, it isn't. It really isn't. The callback version isn't doing any sort of handling of errors, it's just laddering them, passing them back up the chain. The async/await version is doing the same automatically, wiring together for you the error handling to pass errors back to calling functions.
Even if you did want to handle every possible error/exception that could be thrown in doTheThing(), you don't really have to go all the way to the "parade" version, as only one try/catch should be sufficient in doTheThing().
The issue here is that where you see error handling as a cognitive load, the general best practices for error handling have mostly leaned to the goal that exceptions are for actual exceptional cases and catching exceptions should be left to cases where the user can actually fix the exceptional situation (rare) or logged somewhere appropriate in a global handler.
If you are unsure if you should handle an exception, don't handle the exception. Let the calling function catch, or let it continue bubbling up to whatever global exception handlers you want to set, or even just leave them for the browser's Unhandled Exception Handler and Unhandled Rejected Promise Handler to spit out debug information in its dev tools console. Don't bother remembering anything, just let your tools do their job.
The difference between catch (error) and if (error) in the callback example is that if you "forget" catch (error), it bubbles up to your browser's handlers, but if you forget if (error) it doesn't stop execution, it doesn't bubble up to your global toolkits and exception handlers and it doesn't bubble up into your dev tools debuggers.
(One thing I realized in writing this: due to my rustiness in callback coding I forgot that they should be if (error) return callback(error, null) to properly stop execution, which you want in the default case of not handling the error but passing it back up the chain. Small mistake that could cause debugging headache and adds to my point that the little things that are easy to forget that you always must do in the callback world can have a major impact on debugging...)
Eventually, too, Node and Electron and Cordova and everything else will catch up to the fact that Promises are native in ES2015 and the APIs will eventually migrate.
But the thing about it is toi, async functions also return promises, so not every await require "Promise boilerplate", as you will presumably be using other async functions that produce their own promises.
Take this for example:
const response = await fetch('./api.json');
try {
const json = await response.json();
console.log('YAY! JSON!', json);
} catch (jsonParseError) {
alert('damn it!');
}
} catch(fetchError) {
console.warn('this is not cool');
}Another idea is that you could reformat to a "parade" try/catch by adding returns to the catch:
try {
const response = await fetch('./api.json')
} catch (fetchError) {
console.warn('this is not cool')
return // Exit
}
try {
const json = await response.json();
console.log('YAY! JSON!', json);
} catch (jsonParseError) {
alert('damn it!');
}
Obviously you'd need to move any code you'd want to run regardless of error up into the parent function or out into a wrapper function.In short its cleaner code.
> The general rule for deciding which version of Node.js to use is:
> ...
> - Upgrade to Node.js v6 if you have the ability to upgrade versions quickly and want to play with the latest features as they arrive.
> Note that while v6 will eventually transition into LTS, until it does, we will still be actively landing new features (semver-minor). This means that there is an increased chance for regressions to be introduced..
And why would one downgrade from 6.5 to 6.1? If they code for 6.1 and want to not use LTS and to keep 6.1 installations around, then they should not use 6.5 features.
Regardless yes you should use LTS but that's no excuse for going against semantic versioning. It should be labeled an alpha or beta if they don't want to change major versions for breaking feature additions.
Sure, but if you might have to downgrade, then simply don't write code that depends on 6.5 features. 6.5 can still run 6.1 code (feature wise), so you'll be alright.
The only problem would be for people wanting to a) run 6.5, b) take advantage of newer, 6.5-only features, and THEN c) downgrade to 6.1
Ah but see that's the rub. Hindsight is always 20/20. I've actually run into similar issues in the past. Ultimately if they're consistent with versions then it's not the biggest deal (because if you have an issue with 6.5 there is probably a good chance you have an issue with 6.1 as well) but with semantic versioning I always expect code written against the major version to always work across all minor versions unless it relied on some weird bug side effect.
It just seems really inconsistent to me.
I dunno I just don't like to give the impression I'm changing anything about how I'm accessing / using the standard library but I guess this is a bit subjective.
> versus sometimes the "pyramid of doom" callbacks can create
If you structure the code well you never run into this (callback soup is very overblown; if you find yourself in that position then someone screwed something up). But I get promises and async / await are nice :)