How does using async/await differ from using Promises? - javascript

Recently I started using (Async & Await). Before this, I used Promise to make my process Asynchronous. Like:
example.firstAsyncRequest()
.then(firstResponse => {
return example.secondAsyncRequest(firstResponse)
})
.then(secondResponse => {
return example.thirdAsyncRequest(secondResponse)
})
.then(result => {
console.log(result)
})
.catch(err => {
console.log(err)
})
Now I am achieving this like:
try {
const firstResponse = await example.firstAsyncRequest();
const secondResponse = await example.secondAsyncRequest(firstResponse);
const thirdAsyncRequest = await example.thirdAsyncRequest(secondResponse);
console.log(thirdAsyncRequest)
}
catch (error) {
// Handle error
}
In both, the code-block method executes one after another and finally throws an error if any and gets caught by catch block. My question is, is this only a difference of syntax? Please explain or suggest me any link to understand this better.
Thank You

Is there only difference of syntax?
Yes. Your examples are functionally equivalent except that your second example is missing
console.log(thirdAsyncRequest);
...after the third await. (That variable really should be result to match the first code block, or thirdResponse to match the other two response variables).
async/await is syntactic sugar around promise creation and consumption. It's really helpful sugar, but that's all it is. async functions return promises. await expressions let you wait for a promise to settle, either getting the fulfillment value if it's fulfilled or throwing an error if it's rejected. (Since rejections are errors, you can use try/catch to handle them.) An uncaught error in an async function (whether a synchronous error or a promise rejection observed by await) causes the promise the async function returned to be rejected with that error. Otherwise, the function's promise is resolved to whatever the function's code returns: if it returns a non-thenable, the promise is fulfilled with that value; if it returns a thenable, the promise is fulfilled or rejected based on the settlement of that thenable.
(Re "resolve" vs. "fulfill" and such, I've written up this post on promise terminology.)

Related

I'm trying to go step-by-step to see how Promise.prototype.catch receives a Promise. Is my understanding right?

function fetchDog(){
fetch("https://dog.ceo/api/breeds/image/fail")
.then(response => response.json())
.then(data => console.log(data))
.catch(function(err) {
console.log('Fetch problem');
});
};
fetchDog();
Using the above example I would like to clarify how .catch here receives the rejected Promise. It's also a good exercise in reading MDN for me.
1 .
Looking at this statement from MDN:
If the Promise that then is called on adopts a state (fulfillment or
rejection) for which then has no handler, a new Promise is created
with no additional handlers, simply adopting the final state of the
original Promise on which then was called.
I translate that to mean, in my example, the .thens return a new promise that is a copy of the promise that .then was called on.
2 .
The Promise.prototype.catch spec also says it behaves the same as calling Promise.prototype.then(undefined, onRejected).
I interpret this to mean, in my example, that the first callback in catch is the onRejected parameter. Therefore, when catch receives a rejected promise, it executes console.log('Fetch problem');.
I also interpret this to mean that when catch invariably receives a fulfilled promise, it returns undefined? (I haven't thought of a way to test this in the console).
3 .
I also read in the .then spec:
If a handler function: doesn't return anything, the promise returned
by then gets resolved with an undefined value.
Therefore, in my code snippet, I interpret this to mean catch returns a promise whose value is undefined.
Based on this understanding, so long as the fetch line returned a fulfilled promise, this fulfilled promise would find its way to catch and catch's callback wouldn't execute. catch would return undefined. (I can't think of a way to test this). I suspect my understanding is wrong.
When catch invariably receives a fulfilled promise, it returns undefined?
No. Calling .catch() will always return a promise. It does that before even knowing whether the promise that it was called on is fulfilled, rejected or still pending.
I interpret this to mean catch returns a promise whose value is undefined.
Yes. You can test this easily with
function handleError(p1) {
const p2 = p1.catch(err => {
console.log('handling problem', err);
});
p2.then(res => {
console.log('final promise fulfilled with', res);
});
}
// handleError(Promise.resolve('success'));
handleError(Promise.reject('error'));

promise with .catch() rejected but appears as fulfilled in Promise.allSetteled [duplicate]

This question already has answers here:
Chained promises not passing on rejection
(4 answers)
Closed 2 years ago.
Here is the simplified version of the problem;
there are some promises, few .then() chains, and a .catch() block for error handling;
each promise might resolve or reject hence I use Promise.allSetted to know which promise had been failed based on their order in array and their status; It works fine when all promises resolve but when a promise is rejected, it's status will be shown as "fulfilled" in Promise.allSetteld; If I remove the .catch() block, It will work as expected but we need to keep the .catch() block for logging to the store; So why isn't it just behave as expected? is there any way to make it show "rejected" status with .catch() block?
let a = Promise.resolve("a: Promise.resolved").then(response => response).catch(err=>console.log(err));
let b = Promise.reject("b: Promise.rejected").then(response => response); // no error handling
let e = Promise.reject("e: Promise.rejected").then(response => response).catch(err=>console.log(err));
Promise.allSettled([a,b,e]).then( values => console.log(values) );
You can throw the error from the catch block, so that the error is not handled in the catch:
let a = Promise.resolve("a: Promise.resolved").then(response => response).catch(err => console.log(err));
let b = Promise.reject("b: Promise.rejected").then(response => response); // no error handling
let e = Promise.reject("e: Promise.rejected").then(response => response).catch(err => {
console.log(err);
throw err;
//or Reject the Promise
//return Promise.reject(err)
});
Promise.allSettled([a, b, e]).then(values => console.log(values));
This is also discussed in the Mozilla docs:
p.catch(onRejected);
onRejected
A Function called when the Promise is rejected.
This function has one argument: reason The rejection reason. The Promise
returned by catch() is rejected if onRejected throws an error or
returns a Promise which is itself rejected; otherwise, it is resolved.
Yeah now I understand, we .catch() it!
so by using .catch() (or catch in try{}catch(e){}) we are telling the program: "Don't panic, everything is under control", " I'll handle that". and that make sense now. if we handle it without throwing errors it sounds like everything is fine; so yeah, why not; it should be fulfilled

Using for await...of with synchronous iterables

MDN says for await...of has two use-cases:
The for await...of statement creates a loop iterating over async
iterable objects as well as on sync iterables,...
I was previously aware of the former: async iterables using Symbol.asyncIterator. But I am now interested in the latter: synchronous iterables.
The following code iterates over a synchronous iterable - an array of promises. It appears to block progess on the fulfilment of each promise.
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
const promises = [happy, sad]
for await(const item of promises) {
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
The behavior appears to be akin to awaiting each promise in-turn, per the logic shown below. Is this assertion correct?
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
const promises = [happy, sad]
for(let p of promises) {
const item = await p
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
I ask because this pattern of code has an implicit rejection wire-up pitfall that Promise.all and Promise.allSettled avoid, and it seems strange to me that this pattern would be explicitly supported by the language.
window.addEventListener('unhandledrejection', () => {
console.log('unhandled rejection; `sad` was not being awaited at the time it rejected')
})
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('success'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('failure')))
const promises = [happy, sad]
for(let p of promises) {
const item = await p
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "unhandled rejection; `sad` was not being awaited at the time it rejected" (after about zero seconds), and then "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
Yes, it is strange, and you should not do this. Don't iterate arrays of promises, it leads exactly to the unhandled-rejections problem you mentioned. (See also this more specific explanation.)
So why is this supported in the language? To continue with the sloppy promise semantics.
You can find the exact reasoning in this comment of the issue discussing this part of the proposal:
I think we should fall back to Symbol.iterator because our current
Promise semantics are all about allowing sync things to be used as
async things. You might call this "sloppiness". It follows
#groundwater's logic above,
but I just want to spell out the parallels in more detail.
The "chaining" semantics of .then are all about this. You can return a
Promise from .then or a scalar value; it's all the same. You call
Promise.resolve not to wrap something in a Promise, but to cast
something to a Promise--get an asynchronous value when you have
something-or-other.
The semantics of async and await are all about being sloppy as well.
You can slap await on any non-Promise expression in an async function
and everything works fine, exactly the same way, except that you yield
control to the job queue. Similarly, you can "defensively" put async
around whatever you want, as long as you await the result. If you have
a function that returns a Promise--whatever! you can make that an
async function, and, from a user perspective, nothing changes (even
if, technically, you get a different Promise object out).
Async iterators and generators should work the same way. Just like you
can await a value that, accidentally, wasn't a Promise, a reasonable
user would expect to be able to yield* a sync iterator within an async
generator. for await loops should similarly "just work" if a user
defensively marks a loop that way, thinking that they maybe might be
getting an async iterator.
I think it would be a big deal to break all of these parallels. It
would make async iterators less ergonomic. Let's discuss this the next
time async generators/iterators come up on the agenda at TC39.
The sad promise isn't being awaited when it fails - that code needs to finish waiting on happy before it can begin to wait on sad. The sad promise is failing before happy resolves. (Promise.all is a tool better suited to this use-case)

Any problem with catch rejected promise and turn it into resolved with error code?

I am working on a project that always catch rejected promise, turns it into resolved one with error code, like this
return new Promise((resolve, reject) => {
axios({
...
}).then(res => {
resolve(res);
}).catch(err => {
let res = { ... }
res.error = err
resolve(res)
})
})
Then whenever call this api, rejected case is handled like this, i.e. without catch
axios_call(...).then(res => {
if (!res.err) {
//resolve case
} else {
//reject case
}
})
I never handle the rejected promise like this before so I am not sure will it cause any problem or it is just a different code style and works fine too.
I checked the possible duplicated q/a What is the explicit promise construction antipattern and how do I avoid it?
But I believed they are not the same. Because my question is about handling the rejected promise while that question is about deferred object, e.g. errors and rejections are not swallowed in my case.
First of all, avoid the Promise constructor antipattern! Your axios_call code should better look simply like this:
return axios({
...
}).catch(err => {
return { ..., error: err };
});
Then whenever call this api, rejected case is handled without catch. I never had handled the rejected promise like this before so I am not sure will it cause any problem or it is just a different code style and works fine too.
It works, but it's not fine. This style of error handling is weird and really non-idiomatic. It has the same problems as the traditional node-style callback API with separate error and result parameters:
the promise can't know whether you handled the error or not. You will not get any unhandled rejection warnings.
you always must write the code to deal with the res.error, if you want it or not. With normal promise usage, you could just supply separate onFulfill and onReject callbacks, and omitting the latter will get you the sensible default behaviour of forwarding the error instead of just dropping it.
I cannot see any advantages that your style would present, so I would recommend to avoid it and use normal promise rejections.

Can I use multiple 'await' in an async function's try/catch block?

i.e.
async asyncfunction(){
try{
await method1();
await method2();
}
catch(error){
console.log(error);
}
}
Given method1() and method2() are asynchronous functions. Should there be a try/catch block for each await method? Is there an even cleaner way to write this? I'm trying to avoid '.then' and '.catch' chaining.
Using one try/catch block containing multiple await operations is fine when waiting for promises created on the right hand side of the await unary operator:
The await operator stores its parent async functions' execution context and returns to the event loop. Execution of the await operator resumes when it is called back with the settled state and value of its operand.
Upon resumption, await restores the previously saved execution context and returns the operand promise's fulfilled value as the result of the await expression, or throws the rejection reason of a rejected operand.
The try/catch block invocation is part of the execution context both before and after being saved and restored. Hence multiple await operations do not disturb the behavior of an outer try block they share. The catch block will be invoked with the rejection reason of any promise awaited in the try block that is rejected.
If however code awaits multiple existing promises in the same try/catch block and more than one of the promises rejects, an uncaught promise rejection error is generated for all but the first rejection. Thanks to #EyolRoth for supplying this caveat, please read his entire answer in conjunction with this one.
While #traktor answer is correct, I want to add a very important caveat.
The following code snippet is unsafe:
async function foo() {
try {
const p1 = method1();
const p2 = method2();
await p1;
await p2;
} catch (e) {
console.log(e);
}
}
If both promises are rejected, the code will result in UnhandledPromiseRejectionWarning in NodeJS, potentially killing the process if the option --unhandled-rejections is set to throw (which will become the default behavior from Node 15).
Why is this happening?
In the original code of the question, the async methods are invoked sequentially; i.e, if the first method fails (promise rejected), the second method is never invoked.
However, the code from the beginning of this answer invokes both of the async methods in parallel; i.e, the second method is invoked regardless if the first method succeeds or not.
In this scenario, only the await on the promise of the second method is conditioned on the success of the first method. Meaning, if the first method fails, the promise of the second method (which has already started) is never awaited, so if it fails too, it will result in an handled promise rejection.
How to overcome this behavior?
Assuming we wish to handle both rejections in the same way, we can use Promise.all:
try {
const p1 = method1();
const p2 = method2();
await Promise.all([p1, p2]);
} catch (e) {
console.log(e);
}
Only the first promise to be rejected will "trigger" the catch clause, while the second will be silently ignored without crashing the process.
Using Async.js Library
Let's talk about working with the async.js library in order to avoid callback hell.
As per the official website of async.js : Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript.
Async.js provides near about 70 functions in total. For now, we will discuss only two of them i.e, async.waterfall() and async.series().
async.waterfall()
This method is useful when you want to run some tasks one after the other and then pass the result from the previous task to the next task. It takes an array of functions "tasks" and a final "callback" function that is called after all functions in "tasks" array have completed or a "callback" is called with an error object.
async.js library

Categories

Resources