What's wrong with awaiting a promise chain?
Under the hood, async/await is just promises.
That is, when you have some code that looks like:
const result = await myAsyncFunction();
console.log(result):
That's exactly the same as writing:
myAsyncFunction().then(data => {
const result = data;
console.log(result);
});
The reason then - that you shouldn't mix async/await and .then
chains - is because it's confusing.
It's better to just pick one style, and stick to it.
And while you're picking one - you might as well pick async/await - it's more understandable.
async await and promise chain is not waiting to resolve
Mixing async/await
with promise-chaining (.then(...)
) is a really easy way how to overcomplicate your code.
I can see a couple of places that contribute to this not working.
- In
__sendRequest
you are not returning anything. - in the first
.then
you are lacking a return too.
Here is a simplified and fixed code
async __sendRequest(url, method) {
const sess = await Auth.currentSession()
console.log(sess.getIdToken().getJwtToken())
const response = await axios({
method: method,
url: process.env.REACT_APP_USER_URL + url,
headers: {
"X-TOKEN-ID": sess.getIdToken().getJwtToken(),
"addresseeType": "P",
"content-type": "application/json"
}
})
console.log(response)
return response
}
Check the comments of the other answer, to understand about async/await
, returning and call stack https://stackoverflow.com/a/69179763/1728166
Using await in promise chain
The callback is not declared as an async
function. You can only await
a Promise
directly inside of an async
function.
async doSomething(stuff) {
// ...
return functionThatReturnsPromise()
.then((a) => /* ... */)
.then((b) => /* ... */)
.then(async (c) => {
const user = await someService.createUser(stuff, c);
return user;
});
};
Moreover, you shouldn't need to use then
if you are leveraging async
functions.
async doSomething(stuff) {
// ...
const a = await functionThatReturnsPromise();
const b = // ...
const c = // ...
const user = await someService.createUser(stuff, c);
return user;
};
NodeJS, from promise chaining to async/await chaining
The way you have it (commonData = await function1(commonData)
, etc.) is just fine. But if you don't want to do that, you could nest the calls:
const result = await function5(
await function4(
await function3(
await function2(
await function1(commonData)
)
)
)
);
Or use a loop:
let result = commonData;
for (const fn of [function1, function2, function3, function4, function5]) {
result = await fn(result);
}
Or you could keep using then
. Although in general mixing async
/await
with explicit promise handlers via .then
/.catch
is confusing, there are some limited times where you might do it:
const result = await function1(commonData)
.then(function2)
.then(function3)
.then(function4)
.then(function5);
It works because async
functions are just a way to consume and produce promises.
Or with the very big caveat that you know that the functions only modify the state of the object and don't return (fulfill their promise with) a different object, you could do:
await function1(commonData);
await function2(commonData);
await function3(commonData);
await function4(commonData);
const result = await function5(commonData);
but it's a big caveat.
Why async/await and then produce different results
Yep that's just what one would expect.
Remember, await
effectively suspends execution until the awaited operation is complete.
In your example using .then
you return immediately because you did not compose your promise chain such that response
is returned only when the sequence of awaited operations completes. Rather, you return immediately with the undefined
value with which response
was initialized
Thus, the proper way to express your logic, otherwise unaltered, via .then
would be
const getFieldValue = (collection, userid) => {
let response;
return firestore() // notice the return here! This is vital
.collection(collection)
.where("userid", "==", userid)
.get()
.then((querySnapshot) => {
querySnapshot.docs.find((doc) => {
const { name } = doc.data();
response = name;
});
})
.then(() => response) // notice how we resolve our promise chain with the value ultimately assigned
.catch((e) => console.log(e));
};
Promises vs async await behaviour javascript. Concurrency mode
To answer your questions, let me first try to explain how your code is working.
Understanding how your code works
Following steps explain the execution of your code:
Script execution start
Call
disallowConcurrency
function, passing in theinitCmp
as an argument.cmpConcurrentFunction
is assigned the return value ofdisallowConcurrency
functionCall
cmpConcurrentFunction
for the first time, passing in'I am called 1 second later'
as an argument. During this invocation,inprogressPromise
is a resolved promise returned byPromise.resolve()
. Awaiting it pauses the function execution.Call
cmpConcurrentFunction
for the second time, passing in'I am called 2 seconds later'
as an argument. During this second invocation,inprogressPromise
is still a resolved promise returned byPromise.resolve()
. Awaiting it pauses the function execution.Synchronous execution of script ends here. Event loop can now start processing the micro-task queue
Function paused as a result of first invocation of
cmpConcurrentFunction
is resumed, callingthen()
method on the promise returned byPromise.resolve()
. The value of theinprogressPromise
is updated by assigning it a new promise returned byinprogressPromise.then(...)
Function paused as a result of second invocation of
cmpConcurrentFunction
is resumed. From step 6, we know thatinprogressPromise
is now a promise returned byinprogressPromise.then(...)
. So, callingthen()
on it, we are simply creating a promise chain, adding a newthen()
method call at the end of the promise chain created in step 6.At this point, we have a chain that looks like this:
inProgressPromise
.then(() => fn('I am called 1 second later'))
.then(() => fn('I am called 2 seconds later'))Callback function of the first
then
method is called, which in turn calls thefn
argument, which isinitCmp
function. CallinginitCmp
sets up a timer that resolves the promiseinitCmp
returns after 1 second. When the promise is resolved after 1 second,'I am called 1 second later'
is logged on the console.When the promise returned by the first call to
initComp
function in the callback function of firstthen
method, is resolved, it resolves the promise returned by the firstthen
method. This leads to the invocation of the callback function of the secondthen
method. This again calls theinitComp
function, which then returns a new promise that is resolved after another 1 second.
This explains why you see 'I am called 2 seconds later'
logged on the console after 2 seconds.
Now answering your questions:
But i don't understand why this keeps working if i remove the await
inprogressPromise
await inprogressPromise
serves no purpose in your code other than pausing the calls to cmpConcurrentFunction
function. Both calls await the same promise that is returned by Promise.resolve()
.
So the await is not necessary? Why?
Because the output you see on the console is not because of await
, but because of the promise chain (step 7 above) that is constructed as a result of two invocations of the cmpConcurrentFunction
function.
Further more i was expecting this to work :
const disallowConcurrency = (fn) => {
let inprogressPromise = Promise.resolve();
return async (...args) => {
await inprogressPromise;
inprogressPromise = fn(...args);
};
};
The above code doesn't works as your original code because now the promise chain isn't constructed which is key to the output your original code produces.
With this implementation of disallowConcurrency
, your code is executed as explained below:
Call
cmpConcurrentFunction
function, passing in'I am called 1 second later'
as an argumentAwaiting
inprogressPromise
pauses the function executionCall
cmpConcurrentFunction
for the second time, passing in'I am called 2 seconds later'
as an argumentAwaiting
inprogressPromise
pauses the function execution. At this point, both invocations ofcmpConcurrentFunction
are paused and both function invocations are awaiting the same promise that was created as a result of callingPromise.resolve()
.This is the key point here: both invocations of
cmpConcurrentFunction
function are awaiting the same promise that was created byPromise.resolve()
. Why are both invocations awaiting the same promise? Because thecmpConcurrentFunction
is invoked for the second time before the first invocation ofcmpConcurrentFunction
is resumed and calls theinitComp
functionResume the first invocation of
cmpConcurrentFunction
, call theinitComp
function and assign its return value toinprogressPromise
.Calling
initComp
function sets up a timer that resolves the promiseinitComp
function returns after 1 secondResume the second invocation of
cmpConcurrentFunction
, call theinitComp
function and assign its return value toinprogressPromise
, overwriting the current value ofinprogressPromise
which is the promise returned by the first call toinitComp
function.Calling
initComp
function sets up a timer that resolves the promiseinitComp
function returns after 1 second
At this point, initComp
function has been invoked twice, setting up two separate timers that resolve their respective promise after 1 second each.
To summarize the differences between the original implementation of disallowConcurrency
function and this one is that in the original implementation, you have a promise chain which resolves the promises sequentially whereas in the second implementation, you have two separate promises (that don't depend on each other) and they are resolved after 1 second each.
Javascript promises - why aren't these working
The reason is I was using Mocha v3.5.3 (the latest is v9.1.3).
So when I was using Mocha v3.5.3 and the tests are run from the terminal we see:
> ./node_modules/mocha/bin/mocha --timeout 10000 ./test/app-spec2.js
test promises
function promise (then ): 1 333333
function promise (await): 2 333333
function2 NOT A promise: 88 NOT ACTUALLY A PROMISE
const promise (then ): 11
const promise (await): 11
const2 NOT A promise: 99 NOT ACTUALLY A PROMISE
const2 promise (then): 2222
✓ using async (1001ms)
✓ async iife
iife const promise (then): 11
2 passing (1s)
But when I upgrade to the latest Mocha we see:
> ./node_modules/mocha/bin/mocha --timeout 10000 ./test/app-spec2.js
test promises
function promise (then ): 1 333333
function promise (await): 2 333333
function2 NOT A promise: 88 NOT ACTUALLY A PROMISE
const promise (then ): 11
const promise (await): 11
const2 NOT A promise: 99 NOT ACTUALLY A PROMISE
const2 promise (then): 2222
✔ using async (1004ms)
✔ async iife
iife const promise (then): 11
2 passing (1s)
function2 promise (then ): 3 44444444
iife function promise (then): 3 333333
iife function promise (await): 4 333333
iife const promise (await): 11
So you can see all promises are resolving. However you can also see that the promises that were 'missing' are resolving AFTER the test completes.
Interestingly IntelliJ seems to truncate the output after the output (and I never see it) - but this is a separate problem.
I tried adding a 'done' method:
it('using async', async (done) => {
// console.log(`before promise call outside`)
functionPromise(1).then(a => {
console.log(`function promise (then ): ${a}`)
})
...
done()
assert.isTrue(true)
})
But the mis-alignment still exists and i get an error:
Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both
I'm not why this is. But this use of Promises in Mocha tests is not normal and I'm not going to pursue this right now. I seem to have the answer to keep me moving forward with what I was actually working on.
uncaught in promise nested async function promise error handling
there was an error "Uncaught (in promise) stop!!" and the string "stop!!" doesn't get added to the div
You broke the promise chain by introducing a stand-alone async function in the .then()
handler (which throws, but has no .catch()
of its own, which ultimately causes the error you see).
Instead, make the entire .then()
handler async:
function tunggu(waktu) {
return new Promise((resolve) => {
setTimeout((() => resolve(`${waktu.toString()} milisecond has passed`)), waktu);
})
}
const div = document.querySelector('#asd');
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(async (json) => {
console.log(json);
div.innerHTML = 'start<br/>';
const a = await tunggu(2000);
div.innerHTML += a + '<br/>';
throw('stop!!');
const b = await tunggu(3000);
div.innerHTML += b;
})
.catch(error => {
console.log('error occurred', error)
div.innerHTML += error
})
<div id="asd"></div>
Related Topics
How to Add New Array Elements at the Beginning of an Array in JavaScript
Jquery's Jquery-1.10.2.Min.Map Is Triggering a 404 (Not Found)
Difference Between Microtask and MACrotask Within an Event Loop Context
How to Get a JavaScript Object's Class
Working with Select Using Angularjs's Ng-Options
How to Loop Through an Array Containing Objects and Access Their Properties
How to Use Namespaces with Typescript External Modules
How to Access the First Property of a JavaScript Object
How to Pass JSON Post Data to Web API Method as an Object
How to Display Length of Filtered Ng-Repeat Data
How to Format Time Since Xxx E.G. "4 Minutes Ago" Similar to Stack Exchange Sites
Get String in Yyyymmdd Format from Js Date Object
How to Get the Size of a JavaScript Object
Prevent Double Submission of Forms in Jquery
What Is the JavaScript String Newline Character
How to Check If Dst (Daylight Saving Time) Is in Effect, and If So, the Offset