What's Wrong with Awaiting a Promise Chain

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:

  1. Script execution start

  2. Call disallowConcurrency function, passing in the initCmp as an argument. cmpConcurrentFunction is assigned the return value of disallowConcurrency function

  3. Call 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 by Promise.resolve(). Awaiting it pauses the function execution.

  4. 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 by Promise.resolve(). Awaiting it pauses the function execution.

  5. Synchronous execution of script ends here. Event loop can now start processing the micro-task queue

  6. Function paused as a result of first invocation of cmpConcurrentFunction is resumed, calling then() method on the promise returned by Promise.resolve(). The value of the inprogressPromise is updated by assigning it a new promise returned by inprogressPromise.then(...)

  7. Function paused as a result of second invocation of cmpConcurrentFunction is resumed. From step 6, we know that inprogressPromise is now a promise returned by inprogressPromise.then(...). So, calling then() on it, we are simply creating a promise chain, adding a new then() 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'))
  8. Callback function of the first then method is called, which in turn calls the fn argument, which is initCmp function. Calling initCmp sets up a timer that resolves the promise initCmp returns after 1 second. When the promise is resolved after 1 second, 'I am called 1 second later' is logged on the console.

  9. When the promise returned by the first call to initComp function in the callback function of first then method, is resolved, it resolves the promise returned by the first then method. This leads to the invocation of the callback function of the second then method. This again calls the initComp 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:

  1. Call cmpConcurrentFunction function, passing in 'I am called 1 second later' as an argument

  2. Awaiting inprogressPromise pauses the function execution

  3. Call cmpConcurrentFunction for the second time, passing in 'I am called 2 seconds later' as an argument

  4. Awaiting inprogressPromise pauses the function execution. At this point, both invocations of cmpConcurrentFunction are paused and both function invocations are awaiting the same promise that was created as a result of calling Promise.resolve().

    This is the key point here: both invocations of cmpConcurrentFunction function are awaiting the same promise that was created by Promise.resolve(). Why are both invocations awaiting the same promise? Because the cmpConcurrentFunction is invoked for the second time before the first invocation of cmpConcurrentFunction is resumed and calls the initComp function

  5. Resume the first invocation of cmpConcurrentFunction, call the initComp function and assign its return value to inprogressPromise.

    Calling initComp function sets up a timer that resolves the promise initComp function returns after 1 second

  6. Resume the second invocation of cmpConcurrentFunction, call the initComp function and assign its return value to inprogressPromise, overwriting the current value of inprogressPromise which is the promise returned by the first call to initComp function.

    Calling initComp function sets up a timer that resolves the promise initComp 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



Leave a reply



Submit