I am trying to visualize the synchronicity of for...await loops in JavaScript.
It appears that in an async generator function both yield and await will stop progress and cue up a continuation on a microtask.
In other words: yielding is always asynchronous in this context, even if you don't use the await keyword.
Is this correct? What about async generator functions outside of the context of for...await loops?
Below a tight microtask loop is created using then, in an attempt to see how tasks are interleaved.
function printNums() {
let counter = 0
function go() {
console.log(counter++)
if(counter < 10) Promise.resolve().then(go)
}
Promise.resolve().then(go)
}
printNums()
async function asyncFn() {
console.log('inside asyncFn 1')
await null
console.log('inside asyncFn 2')
}
asyncFn()
const asyncIterable = {
async *[Symbol.asyncIterator]() {
console.log('inside asyncIterable 1')
yield '⛱'
console.log('inside asyncIterable 2')
await null
console.log('inside asyncIterable 3')
yield '🐳'
yield '🥡'
}
}
async function printAsyncIterable() {
for await(let z of asyncIterable) {
console.log(z)
}
}
printAsyncIterable()
In other words: yielding is always asynchronous in this context, even if you don't use the await keyword
Yes. Inside an asynchronous generator function, the expression
yield value
behaves as if you had written
await (yield (await value))
You cannot yield promises, and you cannot receive promises, they always get automatically unwrapped by the next() call.
Related
I am trying to convert a normal function to an async generator function, it needs to have the same props and prototype.
The way that I did it so far was by copying all the descriptors from the async generator function and by using Object.setPrototypeOf
function normalFunc () {
//...
}
async function * asyncGenFunc () {
//...
}
Object.defineProperties(normalFunc, Object.getOwnPropertyDescriptors(asyncGenFunc))
Object.setPrototypeOf(normalFunc, Object.getPrototypeOf(asyncGenFunc))
As I understand it, Object.setPrototypeOf is slow even though I can't see the slowness myself. Is there a better way or is this way not slow in the specific scenario and the tip in MDN isn't about this case.
EDIT: As for the why do I want to do this, here is a very basic version of the function I am making:
const errorHandle = function (func) {
return function(...args) {
try {
let result
result = func.apply(this, args)
if (isObject(result) && result.catch !== undefined) {
result = result.catch(err => console.error(err))
}
return result
} catch(err) {
console.error(err)
}
}
}
const handledAsyncGenFunc = errorHandle(async function * (){})
I want handledAsyncGenFunc to behave exactly the same as a the original async generator function, but to be error-handled. Don't judge the example too much, I only added the bare minimum for the question.
From what I can gather your after a kind of middleware for async generators.
This is actually easier than you think, you can just create wrapper function that that just passes re yields inside a loop.
eg.
const wait = ms => new Promise(r => setTimeout(r, ms));
async function *gen1() {
yield 1;
await wait(1000);
yield 2;
await wait(1000);
throw "error in gen1";
}
function *gen2() {
yield 1;
throw "error in gen2 (normal generator)";
}
async function *handleError(fn) {
try {
for await (const x of fn) yield x;
// yield * fn;
} catch (e) {
console.log('Caught a generator error (async or normal):');
console.error(e);
//you could also re-throw here
//if your just want to log somewhere
//and keep program logic
//throw e;
}
}
async function test1() {
for await (const a of handleError(gen1())) {
console.log(a);
}
}
async function test2() {
for await (const a of handleError(gen2())) {
console.log(a);
}
}
test1().finally(() => test2());
One thing to note, when you wrap the function you always assume it's an async generator that returned, even if you pass a normal one. This is because await will work with none Promise's, but of course it's impossible for this to work the other way round. IOW: you will need to use for await (x of y) { on the wrapper function and not just for (x of y) {
This question already has answers here:
Async function without await in JavaScript
(4 answers)
Closed 2 years ago.
due to everyone's help, I got the logic behind async, promise, then, await.
I have one curisosity on top of its basic nature, which is what if
I declare async function but doesn't use await in it.
technically every argument within async function is invisibly capsulated by '.then()'
but how it works would be exactly same as synchronous function execution.
For example,
async function trackUserHandler() {
var positiondata= await getPosition();
var timerdata = await setTimer(2000)
console.log(positiondata, timerdata);
setTimer(1000).then(() => {
console.log('Timer done!');
});
console.log('one');
}
The console below doesn't run till the first two await function is done due to await(s) sitting before this.
console.log(positiondata, timerdata);
What if I don't put any await(s) in the async like below?
async function trackUserHandler() {
var positiondata= getPosition();
var timerdata = setTimer(2000)
console.log(positiondata, timerdata);
setTimer(1000).then(() => {
console.log('Timer done!');
});
console.log('one');
}
I test-run this code and behaves seemigly same as regular function without 'async'.
Of course behind the scene, everything in the function is encapsulated into 'then()' though.
Am I understanding right?
Thank you in advance.
Behind the scene- If I use Aync but doesn't use await in it, would it be identical with normal function?
Yes, you are right. An async function without an await expression will run synchronously and would be identical with normal function, but the only difference is that async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
For example, the following:
async function foo() {
return 1
}
...is equivalent to:
function foo() {
return Promise.resolve(1)
}
If there is an await expression inside the function body, however, the async function will always complete asynchronously.
For example:
async function foo() {
await 1
}
...is equivalent to:
function foo() {
return Promise.resolve(1).then(() => undefined)
}
Code after each await expression can be thought of as existing in a .then callback.
Yes, an async function runs synchronously till the first await, so it behaves like a regular function. It does return a Promise though:
function trackUserHandler() {
// ... code
return Promise.resolve(undefined);
}
In your example, the 2 functions won't behave the same. Without the await keyword, your variables won't capture the results returned by these 2 functions, but instead receive 2 Promises.
var positiondata = getPosition();
var timerdata = setTimer(2000);
So your console.log will print out 2 Promises instead of the values you actually expect.
An async function can contain an await expression, that pauses the
execution of the async function and waits for the passed Promise's
resolution, and then resumes the async function's execution and
returns the resolved value.
As you assumed, if no await is present the execution is not paused and your code will then be executed in a non-blocking manner.
const getPromise = async (s) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(s), 500);
});
}
(async() => {
try {
const result = getPromise("a"); //no await, result has not been unwrapped
console.log('async/await -> ', result);
} catch (err) {
console.log(err);
}
})();
Async function without await in Javascript
I have a sudoku board implemented as an HTML table and a button that when clicked solves the sudoku board using a recursive backtracking algorithm in javascript. Now I want to make it so that you can see the adjustments being made by not altering the HTML immediately in my recursive function. I tried making the function async and then calling this function
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
like so
for (let choice = 1; choice <= 9; choice++) {
this.boardArray[row][col] = choice;
// put delay here
await sleep(250);
currEntry.textContent = choice;
if (this.choiceOkay(row, col)) {
const solved = this.solveBoard(nextRow, nextCol);
if (solved) {
return true;
}
}
This does give me the desired behavior initially but only for a portion of the board and then it seems to just stop. I removed the calls to the sleep function and tried it with the only alteration being "async" in front of the function declaration and it still only did a portion of the board but this time all at once without the visual delay. I'm wondering why making this function async causes this logic error?
Thanks in advance!
Also, this is my first question on Stack Overflow so let me know if I need to be more specific or anything along those lines.
The async keyword in front of a function basically says:
This function will return a Promise
Your sleep function already returns a Promise, so writing the keyword async is useless.
What matters is the await keyword which basically says:
On my right, there may be an async function (idem a Promise). Wait for my return before continuing
If you write async function sleep, but omit the await keyword when you call sleep, you throw the function call in the nature and never waits for its return to come back (hence your code running "without delay")
If you want delay, it is better to let your algorithm be as is, and let the caller of your algorithm tell your algorithm to continue or not.
You can for that matter use generators.
(An other possibility could be trampolining).
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield; // gives control back to delayer
console.log('choice : ', choice)
// do your algo and put some yield wherever you like
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
}
}
delayer()
However, it is very likely that solving your board is what freezes your ui.
So you want to wait inside of solvingBoard as well:
function* solveBoard () {
let i = 0
while (i < 5) { //freezes the ui if not yielding
yield i++
}
}
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield 'choice : '+choice; // gives control back to caller
yield* solveBoard()
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
console.log('data', next.value)
}
}
delayer()
MDN has an example in which a generator function is marked as async, and yield is used without await.
Why is await not necessary in this example?
const myAsyncIterable = {
async* [Symbol.asyncIterator]() {
yield "hello"
yield "async"
yield "iteration!"
}
}
Just because an function is async doesn't mean that await is required. I think this example has async just to show that you could easily insert an awaited Promise into it.
Something with a Symbol.asyncIterator can be iterated over asynchronously if needed, but it doesn't have to have any asynchronous operations. You could even replace the async function with a standard generator function, on the Symbol.asyncIterator property, and still have things work just fine:
const myAsyncIterable = {
*[Symbol.asyncIterator]() {
yield "hello"
yield "async"
yield "iteration!"
}
};
(async () => {
for await (const item of myAsyncIterable) {
console.log(item);
}
})();
Looking at the specification, after the iterator is retrieved from the object, it doesn't matter whether the iterator is sync or async. An async generator function will result in an async iterator; a sync generator function will result in a sync iterator. See the final step 7 of the spec here - GetIterator will be invoked, and whatever object that's on Symbol.asyncIterator will be returned, if it exists, without regard to whether it's actually async or not.
With the iterator, when iterated over with for await, each value returned by the iterator will be awaited:
b. If iteratorKind is async, then set nextResult to ? Await(nextResult).
And it's perfectly fine to await something that isn't a Promise, it's just odd:
(async () => {
const item = await 'foo';
console.log(item);
})();
await will unwrap a Promise if the expression to its right is a Promise, and will leave it unchanged otherwise.
With a synchronous JavaScript generator I can iterate over it as follows:
(() => {
function * syncGenerator () {
yield 1
yield 2
yield 3
console.log('done')
}
Array.from(syncGenerator())
})()
This will simply iterate over the whole generator without having to initialise a variable. I would like to do the same with async generators. The closest solution I could come up with is as follows:
(async () => {
async function * asyncGenerator () {
yield Promise.resolve(1)
yield Promise.resolve(2)
yield Promise.resolve(3)
console.log('done')
}
for await (const num of asyncGenerator()) {}
})()
Unfortunately I had to instantiate the variable num in the above code snippet. This causes StandardJS to give an error on that line, because the variable isn't used. Is there any way I can iterate over an async generator without having to create a variable?
Current solution
Based on the comments to the question and my own research my preferred solution to the problem at the time of writing is the following:
(async () => {
async function * asyncGenerator () {
yield Promise.resolve(1)
yield Promise.resolve(2)
yield Promise.resolve(3)
console.log('done')
}
// eslint-disable-next-line no-unused-vars
for await (const num of asyncGenerator()) {}
})()
Note the // eslint-disable-next-line no-unused-vars comment which suppresses the warning generated by StandardJS for that one line.
Future solution
Once the Iterator Helpers proposal matures and becomes available one could do something like the following for both synchronous and asynchronous generators:
function * syncGenerator () {
yield 1
yield 2
yield 3
console.log('sync done')
}
syncGenerator().forEach(() => {}) // Logs 'sync done'
async function * asyncGenerator () {
yield Promise.resolve(1)
yield Promise.resolve(2)
yield Promise.resolve(3)
console.log('async done')
}
asyncGenerator().forEach(() => {}) // Logs 'async done'
Here's another method
async function * asyncGenerator(): AsyncIterableIterator<number> {
yield Promise.resolve(1)
yield Promise.resolve(2)
yield Promise.resolve(3)
console.log('async done')
}
const it: AsyncIterableIterator<number> = asyncGenerator()
while (true) {
const result: IteratorResult<number> = await it.next();
if (result.done) break;
}
(TypeScript types added for documentation purposes)
Of course, this does declare the variable it, but it's not unused, which I believe is what your actual problem was.
That said, my ESLint did complain about the use of while (true) (with config eslint:recommended), which I personally find a bit stupid, but may or may not be a problem for you.