Synchronous exceptions in async functions - javascript

What happens if an async function throws an exception synchronously?
Example:
async function whatHappen () {
throw new BombError()
// no "await" here
}
try {
whatHappen().catch(() => console.error('rejected'))
} catch (e) {
console.error('thrown')
}
I've run this example in Babel and it seems that the throw is automatically caught and translated into a rejected promise, so the example will log "rejected" to the console.
But does this reflect the actual specification and how it will be implemented in JavaScript engines? I've tried reading the technical proposal but the specification is obviously not aimed at language users but language implementers.
Can I rely on async functions to always return a promise or are there situations where they might throw an exception synchronously? Is there any scenario where calling an async function without await should be wrapped in a try/catch block?

Yes, an async function always returns a Promise.
In the technical proposal that you linked to, there is a sentence in the beginning stating that:
A similar proposal was made with Deferred Functions during ES6 discussions. The proposal here supports the same use cases, using similar or the same syntax, but directly building upon control flow structures parallel to those of generators, and using promises for the return type, instead of defining custom mechanisms.
(Emphasis mine)
So there is no case where you need to wrap an async function in a try/catch block, as it cannot throw a synchronous error.
PS: I just saw that Chrome Canary and MS Edge have implemented async/await behind a flag, so you could test it there as well.

Related

Why doesn't .then() need the async keyword when used (similar to await)? How does Javascript know it's an asynchronous operation?

I'm starting to learn asynchronous Javascript and I'm really confused.
To be honest, the async/await approach seems very logical to me. We need to let the runtime know we're doing an asynchronous operation so it can handle it accordingly. But why don't we need to do the same when using the .then() method? I mean, if Javascript was already able to understand when promises are handled, couldn't await just be used without async just like .then()?
To make it even more confusing, I saw people using .then() directly inside functions declared with the async keyword. Wasn't async/await supposed to be syntactic sugar for the .then().catch() approach? Why can these be combined, especially inside one another? Using .then() on the result of the async function wouldn't have been as confusing, but being inside one another makes me have even a harder time understanding this.
I really searched everywhere for an explanation on this and couldn't find an answer for this exact question. All I've found was people saying you can use both approaches because they essentially are the same thing, but when you get in the details, things aren't very clear.
So the async function always returns a promise. Inside it, await always handles promises. .then() can be chained to the await function. .then() can also be chained to the result of the async function. Same with the .catch method if we don't want to use try/catch on the await. Why is it so mixed up? Can we handle the return of async without .then()? If async/await really is syntactic sugar for .then(), why doesn't .then() also always return a promise after it resolves?
If anybody can help with some clarification, I would truly appreciate it. Thank you!
There's so many questions here, but one way to think about it is... Promises are not asynchronous operations, they are just a standard pattern that helps users deal with existing asynchronous operations in a predicable way.
There's nothing special about .then(). It's not a Javascript keyword, and javascript doesn't need to be 'aware' of then in any way.
Promises are a pattern. You can write a promise class yourself from scratch (I'd highly recommend this). When you use .then(), and you pass it a function, you are telling the promise class: "When the promise resolves, please call this function for me".
All of this existed before async/await. Async/await was added after to make it easier to work with promises.
I find the question if 'something is syntactic sugar' meaningless. It suggests that a language feature is not that meaningful because the same thing could be accomplished before the language feature existed. While this might be true, this is true for any programming language when compared to, say, assembly. To me the definition of 'syntax sugar' can be extended to almost anything, so it's not a useful designation.
What async/await (and generators before it) adds to the language, is that a javascript function can be interrupted and resumed later after some condition.
Lastly, .then() and .catch() always return promises. If you see a .then() function that doesn't, then it's not compatible with the Promise/A+ spec.
If you are willing to put in the work to fully understand this, I would recommend the following 2 exercises:
Write your own Promise class
Re-implement async/await using generator functions (see the co package of how this was done before async/await landed)
The purpose of async/await is to allow writing async code in a serial manner, which is mentally simpler to reason about (for some human-beings). This is useful, if you need to wait for async operation to finish before you continue with the rest of the code. For example, if you need to pass result of async operation as parameter.
Example 1
function asyncOperation1(n) { return Promise.resolve(n+1); }
function asyncOperation2(n) { return Promise.resolve(n/2); }
function asyncOperation3(n) { return Promise.resolve(n*3); }
function errorHandler(err) { console.error(err); }
function main() {
// flow-control
asyncOperation1(1)
.then(asyncOperation2)
.then(asyncOperation3)
.then(continueAfterAsync)
.catch(errorHandler)
// function wrapper
function continueAfterAsync(result) {
console.log(result);
}
}
main();
With async/await the code of the main function above may look like
async main() {
try {
console.log(
await asyncOperation3(
await asyncOperation2(
await asyncOperation1(1)
)
)
);
} catch(err) {
errorHandler(err);
}
}
Pay attention that we don't need to rewrite async operation functions to be async function asyncOperation... to use await, but we need to declare main function as async main.
Which one is better(?) is the mater of developers's taste and previous programming languages experience. The benefit that I can see is that you don't need to wrap everything into functions and introduce additional flow-control code, leaving this complexity to JavaScript compiler.
However, there are cases, when you want to schedule some parallel tasks and you don't care which one will finish first. These kind of things would be relatively hard to do with async/await only.
Example 2
function main() {
Promise
.all(
['srv1', 'srv2', 'srv3'].map(
srv => fetch(`${srv}.test.com/status`)
)
])
.then(
responses => responses.some(res => res.status !== 200) ?
console.error('some servers have problems') :
console.log('everything is fine')
)
.catch(err => console.error('some servers are not reachable', err))
}
So, we see that there is a room for both .then() and await to coexist.
In some cases function may be either synchronous or asynchronous, depending on business logic (I know it's ugly, but in some cases it's unavoidable). And here we come to your main question
why don't we need to mark an asynchronous operation with .then() and we have to do it with await
In other words, why do we need async keyword at all?
Example 3
// without `async`
function checkStatus(srv) {
if (!srv.startsWith('srv')) {
throw new Error('An argument passed to checkStatus should start with "srv"')
}
return fetch(`https://${srv}.test.com/status`);
}
function main() {
// this code will print message
checkStatus('srv1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
// this code will fail with
// Uncaught TypeError: (intermediate value).then is not a function
checkStatus('svr1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
}
However, if we define async function checkStatus, compiler will wrap the runtime error into rejected promise return value, and both parts of the main function will work.
Now let's imagine that JavaScript allows to write functions that use await without specifying async in front of them.
Example 4 (not a valid Javascript)
function checkStatus(srv) {
if (cache[srv]) {
data = cache[srv];
} else {
data = (await fetch(`https://${srv}.test.com/status`)).json();
}
data.x.y = 'y';
return data;
}
What would you expect checkStatus to return? Promise, raw value or throw exception (in case data.x is undefined)?
If you say Promise, then it would be hard for developer that uses this function to understand why inside of checkStatus one can write data.x and outside of it (await data).x is required.
If raw value, the whole execution flow becomes cumbersome, and you can no longer rely on the fact that JavaScript is a single-threaded language, where no-one can change the value of the variable between two lines of code that are written in serial manner.
As you noticed, async/await is a syntactic sugar. If this syntax allows me to avoid possible runtime errors at earlier stage and keep the language backward compatible, I'm eager to pay the price of putting extra async in front of async functions.
Also, I would recommend to read the answers to JS async/await - why does await need async?
In a simple explanation, async/await is syntactic sugar (the node interpreter/compiler/optimizer will convert everything to normal Promises).
The goal of this feature is turn our life easy, because the callback way/style of programming eventually lead us to make mistakes. We call this "the callback hell"
So, we can use .then() when calling functions that is decorated with async keyword, and we can await on functions that return Promise objects.
It's important for optimizations in general, that the code we write tells the compiler what we are meaning, in terms of performance. Imagine if all our codes / lines of code / instructions could be async or sync at the same time. This would lead the computers perform bad, because the task of check this during runtime is very expensive.
So that's why it's so important to us code instructions in an efficient manner.

Why should we wrap await inside an async function?

Why should we have an async function in order to use await? Why can't we just use await without async? JS is async by default too, this just adds to the confusion.
Update:
I've seen some lads put my question on hold so I'll try to elaborate.
I'm just curious as to why this won't work:
some code
let users = await getUsers();
some code
Why should it be inside an async for it to work, ie
$(async function() {
some code
let users = await getUsers();
some code
});
JS is async by default too...
No, JavaScript is not async by default. The only async features of JavaScript are fairly newly-added:
Promise resolution
async/await
JavaScript is commonly used in environments where you interact with asynchronous things (like event handlers in the DOM, or I/O completions in Node.js), but JavaScript is not asynchronous (other than above).
In the words of Allen Wirfs-Brock, who was the editor of the ECMAScript specification for many years, JavaScript...
(has) an observably synchronous execution model. Other than via Atomics/SABs there are no observable shared-state race conditions.
Back to your question:
Why should we have an async function in order to use await?
Before too long, with modules you won't have to, once the top level await proposal finishes working through the process. It just got to Stage 3.
But the answer is that await is syntactic sugar for consuming a promise, and one of the rules of promises is that you either handle errors or return the chain to the caller (so it can handle errors or return the chain to its caller). await doesn't handle errors, so it has to return the chain to the caller. The way it does that is that an async function always returns a promise, and that promise is chained to the promise await awaits.
That is, this:
async function foo() {
const thingy = await somethingAsyncReturningAPromise();
return thingy.foo;
}
is conceptually (but not literally) this:
function foo() {
return somethingAsyncReturningAPromise()
.then(thingy => thingy.foo);
}
If something goes wrong in somethingAsyncReturningAPromise, the promise returned by foo rejects — the error is propagated to the caller.
As far as I can tell from the top-level await proposal, it simply allows unhandled rejections at the top level of the module to be unhandled rejections. So just like this code causes an unhandled error:
null.doSomething();
this code in an async module would cause an unhandled rejection:
await somethingThatReturnsAPromiseAndRejects();
Why should we have an async function in order to use await? Why can't we just use await without async?
Because async/await is "just" syntactic sugar for Promises. If the function is async, then it returns a Promise. It is not possible to have the "await" behaviour without returning a promise. The fact that the function is async has to be marked explicitly.
JS is async by default too, this just adds to the confusion.
This statement is too "simplified". While it is true that JS is async in nature, because of the event loop, this doesn't mean that every function has an async behavior. This does not add to the confusion. You're probably confused due to misunderstanding how JS really works. You should read about Promises, which are behind the scenes when you see async/await.
JavaScript has task based concurrency. It basically means that code blocks (tasks) runs synchronously without being interrupted, until it reaches a certain breakpoint (the end of a task). That has some advantages:
1) You do have concurrency, e.g. a network call does not block your script from doing other things in the meantime (so the task that consumes the network request will only run if the network request was done, other tasks can be done in the meantime).
2) On the other hand, you do not have concurrent mutations, which eliminates a lot of problems (let a = 1; a += 1; could evaluate to 3, you would need locks / semaphores to prevent those, c.f. Java & others).
Now async / await allow you to define such tasks:
An async function can be divided into tasks, await serves as a breakpoint:
let a = 1;
async function stuff() {
a = a + 1; // this is totally secure, as no other code might run in the meantime
a = a + await other(); // this is unsafe, as we await, which means that other tasks might be executed in the meantime.
}
If you want to "await in non async functions" that basically means that you won't know wether a certain function call runs synchronously (meaning: without other code running in the meantime) or not:
function dangerous() { await stuff(); }
let a = 1;
a = a + dangerous(); // does that work or not? Can a be manipulated in the meantime?
So with your proposal you would basically remove the borders around tasks, every code might run every inbetween. So at the end that causes chaos, and chaos is not good if you want to be productive.

How to handle error and use promises correctly

To begin with, I am taking a follow up from this question I posted few moments ago
Now, I thought I knew Aysnc and Promise but clearly I am missing something.
Referencing to the Tagged answer by estus,
Unless API supports promises, errors should be entirely handled in
async function. Function body should be wrapped with try..catch to
rule out unhandled rejections which may result in exceptions in future
Node versions
From which I was able to comprehend that whenever we are using aysnc function and we want to do error Handling we need to use try..catch and for normal Promises we could simple do resolve/reject or if it is already a promise then we can chain and do .then and .catch but for that I got following reply on comment
Yes, you can expect an error. async/await is just syntactic sugar for
raw promises. Any async function can be rewritten in plain ES6
I might be keeping this question broad but can someone please help me in explaining..
When do we need to use
.then and .catch
and when do we need to use
try..catch
Also, What does it mean by
Unless API supports promises
Async await code looks cleaner and easy to read. The Promises were created to solve the callback hell problem but chaining a lot of promises also confusing. So async wait is a syntactical sugar and you can use any one of the .then or async await.
If you are using simple promises syntax then you can use .then.then.then.catch() syntax.
if you are using async and await then you have to use try catch. All the await will go in try and the catch condition would go in single catch.
Both these can be used when API/function you are using returns a promise.
try...catch in async function is syntactic sugar for catch() in raw promise. If raw promises are used for some reason (legacy code) then catch() may be used. This shouldn't be a problem in Node, since recent versions support async..await.
Notice that try..catch catches both synchronous and asynchronous errors in async. This should be taken into account to not leave synchronous errors unhandled with plain promise.
If API doesn't support promises, you cannot expect that promise rejection that is returned from a function be handled by API, so you need to do this yourself.
// So this is how promises work (as you may already be familiar with)
function promiseFu() {
return new Promise((resolve, reject) => {
reject();
})
.catch(e => {
// The exception has been handled
console.error("Error begin in promiseFu()!")
throw e; // <- now this triggers the second exception
})
}
// Asynchronous functions are the functions that ALWAYS returns a promise
// therefore, this is also correct
async function asyncFu() {
return new Promise((resolve, reject) => {
reject();
})
.catch(e => {
// The exception has been handled
console.error("Error begin in promiseFu()!")
throw e; // <- now this triggers the second exception
})
.catch(e => {
// Here the second exception gets handled as well, and asyncFu does not throw any exception in the end
})
}
// Now the power of async await
async function asyncMainFu() {
// using await before an async function would make it wait for the function to complete asynchronously before moving on
await asyncFu()
// await would do the same with promises as well
// await promiseFu() // <- this is correct as well
// BUT now, if you see promiseFu() is throwing the second exception which is not yet handled,
// asyncMainFu() would throw the same exception as well. unless handled by a try..catch block
try {
await promiseFu()
} catch(e) {
// handling the exception thrown by promiseFu and not throwing any new exception
// is a good idea if you think caller of asyncMainFu() might not handle it.
}
}

Why is it possible to try-catch an async-await call?

There is a common anti pattern in JavaScript:
function handleDataClb(err, data) {
if(!data) throw new Error('no data found');
// handle data...
}
function f() {
try {
fs.readFile('data', 'utf8', handleDataClb);
} catch(e) {
// handle error...
}
}
This try-catch in f will not catch errors in the handleDataClb as the callback is called in a later stage and context where the try-catch is not visible anymore.
Now in JavaScript async-await is implemented using generators, promises, and coroutines, as in:
// coroutine example
co(function* doTask() {
try {
const res1 = yield asyncTask1(); // returns promise
const res2 = yield asyncTask2(); // returns promise
return res1 + res2;
} catch(e) {
// handle error...
}
});
// async-await example
async function doTask() {
try {
const res1 = await asyncTask1(); // returns promise
const res2 = await asyncTask2(); // returns promise
return res1 + res2;
} catch(e) {
// handle error...
}
}
This way the try-catch works, which is often mentioned as one great advantage of async-await over callbacks.
Why and how does the catch work? How does the coroutine aka async manage to throw the error inside the try-catch when one of the asyncTask calls results in a promise rejection?
EDIT: as others have pointed out, the way how the JavaScript engine implements the await operator can be very different from the pure JavaScript implementation used by transpilers like Babel and shown above as coroutine example. Therefore to be more specific: how is this working using native JavaScript?
Why and how does the catch work? How does the coroutine aka async manage to throw the error inside the try-catch?
A yield or await expression can have 3 different outcomes:
It can evaluate like a plain expression, to the result value of that
It can evaluate like a throw statement, causing an exception
It can evaluate like a return statement, causing only finally statements to be evaluated before ending the function
On a suspended generator, this can be achieved by calling either the .next(), .throw() or .return() methods. (Of course there's also a 4th possible outcome, to never get resumed).
…when one of the asyncTask calls results in a promise rejection?
The awaited value will be Promise.resolve()d to a promise, then the .then() method gets invoked on it with two callbacks: when the promise fulfills, the coroutine is resumed with a normal value (the promise result), and when the promise rejects, the coroutine is resumed with an abrupt completion (exception - the rejection reason).
You can look at the co library code or the transpiler output - it literally calls gen.throw from the promise rejection callback.
async funtions
An async function returns a promise that is resolved by a value returned by the function body, or rejected by an error thrown in the body.
The await operator returns the value of a fulfilled promise or throws an error, using the rejection reason, if the awaited promise is rejected.
Errors thrown by await can be caught by try-catch blocks inside the async function instead of allowing them to propagate up the execution stack and rejecting the promise returned by calling the async function.
The await operator also stores the execution context before returning to the event loop, in order to allow promise operations to proceed. When internally notified of settlement of the awaited promise, it restores the execution context before proceeding.
A try/catch block set up in the async function's execution context is not altered or rendered ineffective simply because the context has been saved and restored by await.
As an aside
"async-await is implemented using generators, promises, and coroutines"
may be part of how Babel transpiles async function and await operator usage, but native implementations could be implemented more directly.
Generator functions (Update)
A generator function's execution context is stored in its associated generator object's internal [[Generator Context]] slot. (ECMA 2015 25.3.2)
Yield expressions remove the generator's execution context from the top of the execution context stack (25.3.3.5 of ES6/ECMAScript 2015)
Resuming a generator function restores the the function's execution context from the generator object's [[Generator Context]] slot.
Hence generator functions effectively restore the previous execution context when a yield expression returns.
Throwing an error within a generator function for normal reasons (syntax error, a throw statement, calling a function which throws) can be caught by a try-catch block as expected.
Throwing an error by means of Generator.prototype.throw() throws an error within the generator function, originating from the yield expression that last passed control from the generator function. This error can be trapped by try-catch as for ordinary errors. (Refs MDN using throw(), ECMA 2015 25.3.3.4
Summary
Try-catch blocks around yield statments used in await transpilation code work for the same reason they do around await operators within native async functions - they are defined in the same execution context as the error is thrown for a rejected promise.

Why does jQuery's promise have a done(), but Javascript's promise documented by Mozilla does not? What if I want to have a done() in JS?

What are the differences between Mozilla's JavaScript docs' Promises (see API page) and jQuery's Promises (see API page)?
Mozilla's promise seems to have only 2 methods: then and catch. jQuery's promise seems to have more methods, including: then, done, and fail. (from here)
How come the JS API on Mozilla doesn't have done()? What if I want to have a done() functionality in JavaScript? What do I do?
Mozilla's javascript promises are based on ES6 standard, whereas jQuery promises were something created before ES6 was released.
Based on my reading of the jQuery docs, ES6 then is equivalent to jQuery done.
There are actually a boatload of promise libraries, but to me the ES6 one is the simplest to understand. You don't need more than "then" and "catch" and it is real easy to chain together into a sequence of operations. Add to that with Promise.all for parallel tasks and 99% of what you need is covered.
return doSomething().then(function(result) {
return doSomethingElse(result);
}).then(function(secondResult) {
return doThirdSomething(secondResult);
}).catch(function(err) {
console.log(err);
}).then(function(finalResult) {
// a then after a catch is like a "finally" or "always"
return finalResult;
});
Some things that jQuery does support that is not in ES6 is some sort of "progress" resolve.
jQuery's deferred API is bloated and predates promise libraries. Once they realised how useful promises were, they added a then (or previosly, pipe) method, however it they failed to get it 100% right.
How come the JS API on Mozilla doesn't have done()?
It's completely unnecessary. All Promises/A+ compatible implementations (which includes ES6) only need a single method: .then(). It's completely universal, you can do everything with it - it's the primitive of promises.
What if I want to have a done() functionality in JavaScript? What do I do?
Well, you could implement it yourself:
Promise.prototype.done = function(cb) { // or function(...cbs) for (let cb of cbs) …
this.then(cb).then(null, function(err) { /* ignore */ });
return this;
};
But as you can see from that, it's not very useful actually. It doesn't chain, and it ignores exceptions, so you should just use then everywhere.
I can speak for why ES6 don't have .done. However, you can still have something that's akin to .done.
yourPromiseObject.then(successHandler).catch(failureHandler).then(doneHandler);
The first then handles a success response, but will not be called with the yourPromiseObject's function rejects or throw exception. The second catch is critical in "resolving" any exceptions/rejection (in short, you tell the promise object to keep going, you know that there's an error). Make sure you return something valid, even something as simple as the failureHandler consist of nothing but a return 0 will work. The final .then is now guaranteed to be called, so that's your .done

Categories