UPDATE: Upon reflection, it appears that my previous approach was influenced by a habit of using await at the top level. It's important to understand why the while loop failed to function as intended. For more information on why using while
can be problematic for the event loop and fail to execute properly, refer to this explanation:
The main issue with the while() loop is that it continues running until the flag changes its value. While this loop is active, it blocks the event loop. This means that even though there's a setTimeout() scheduled to fire in 1 second, it cannot trigger its callback function until the interpreter returns control to the event loop.
In essence, the done value never gets updated because the loop is effectively trapping the program inside it, leading to an infinite loop scenario.
To achieve a similar syntax as desired, utilizing a callback is recommended. It's essential to explicitly handle the result within the functionThatCannotBeAsync
.
functionThatCannotBeAsync() {
const awaiting = someAsyncStuff() //This is a promise
awaiting.then((value) => {
//perform actions with the value,
return value
})
}
It's worth noting that this code does not block the thread!
//Some other part of the code
console.log(typeof functionThatCannotBeAsync() === undefined)
//true <- Not the expected outcome!
This scenario is undesirable 99% of the time.
Instead, opting for a callback approach, where a callback function is passed as a parameter, is more favorable. The type of value
corresponds to what is anticipated from the await function. This pattern aligns with the purpose of async/await, aiming to mitigate issues like callback hell. It signifies that any dependent code relying on the awaited value must exist within the callback function in order to avoid falling into a callback hell situation, provided this function is the only one unable to be asynchronous.
functionThatCannotBeAsync(callback: (value: any) => void) {
const awaiting = someAsyncStuff()
awaiting.then((value) => {
const newValue = value+'bar'
callback(newValue)
})
}
Subsequently, in another section of the code:
functionThatCannotBeAsync((value) => {
console.log("I am code dependent on value!")
//perform actions based on the value
})
console.log("I am code not dependent on value!")
// logs:
// 'I am code not dependent on value!'
// 'I am code dependent on value!'
An alternative method closely resembling JayCodist's suggestion involves manipulating the result within the functionThatCannotBeAsync
, subsequently returning a promise elsewhere to enable the use of await in that location.
function functionThatCannotBeAsyncPromise() {
const awaiting = someAsyncStuff() //This is a promise
const promise = new Promise((resolve, reject) => {
awaiting.then((value: string) => {
const newValue = value+'bar'
resolve(newValue)
}).catch((reason) => {
throw new Error(reason)
//alternatively, we could use reject
//reject(reason)
})
})
return promise
}
//...
//Elsewhere in the code
const value = await functionThatCannotBeAsync();
Here are functional examples on TS playground
For further reading:
The original response is retained below for historical purposes or reference...
An alternative technique involves utilizing an immediately invoked anonymous expression, known as IIFE, to convert an asynchronous function into a synchronous one.
With an async IIFE, you can employ await and for-await even in older browser environments and JavaScript setups lacking support for top-level await:
This assumes no intermediate operations are necessary between calling the IIFE and receiving its output
functionThatCannotBeAsync() {
(async () => await someAsyncStuff())()
// wait for the operation above to complete
// return with the result
}