Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why does AsyncFromSyncIterator exist? #1765

Closed
bakkot opened this issue Oct 29, 2019 · 5 comments
Closed

Why does AsyncFromSyncIterator exist? #1765

bakkot opened this issue Oct 29, 2019 · 5 comments
Labels

Comments

@bakkot
Copy link
Contributor

bakkot commented Oct 29, 2019

AsyncFromSyncIterator is a spec fiction converts sync iterators into async iterators, i.e., iterators which return promises. It is not accessible to ECMAScript code (as of #1474).

It is used in two places: in for-await-of loops and in yield* in async generators. Specifically, it is used when those constructs are given objects which have Symbol.iterator but not Symbol.asyncIterator.

But both of those places always Await the result of the NextMethod of the iterator they're working with, and that macro wraps its argument in a Promise anyway. Indeed, if you make an object which has a sync iterator in its Symbol.asyncIterator property, that will work fine:

let asyncArray = [0, 1];
asyncArray[Symbol.asyncIterator] = Array.prototype[Symbol.iterator];

// this logs `0 false`, `1 false`, and `undefined true`

async function* yieldstar() {
  yield* asyncArray;
}
let iter = yieldstar();
iter.next().then(a => { console.log(a.value, a.done); });
iter.next().then(a => { console.log(a.value, a.done); });
iter.next().then(a => { console.log(a.value, a.done); });


// this logs `0` and `1`

async function forawait() {
  for await (let value of asyncArray) {
    console.log(value);
  }
}
forawait();

As far as I can tell, the only only observable difference made by wrapping is that it causes there to be additional PromiseJob queue items (although it seems there is not broad agreement among the engines about exactly how many).

So what's the point of wrapping the sync iterator? Is it just to cause these additional queue items?

@ljharb
Copy link
Member

ljharb commented Oct 30, 2019

When you say “not broad agreement”, can you provide some more detailed comparisons?

@bakkot
Copy link
Contributor Author

bakkot commented Oct 30, 2019

Sure, here's a gist.

Roughly speaking, JSC and V8 have 1 additional queue item per step, SpiderMonkey has 2, and XS has 0 (i.e., it behaves as if it does no wrapping at all).

@devsnek
Copy link
Member

devsnek commented Nov 12, 2019

QJS also has one tick, and the iterator proposal is planning to use AsyncFromSyncIteratorPrototype so that you don't have to worry about whether or not you've wrapped a sync iterator when you do AsyncIterator.from().

@ExE-Boss
Copy link
Contributor

It has the additional feature of unwrapping yielded promises from synchronous iterators:

function *foo() {
	for (let i = 1; i <= 3; i++) {
		yield Promise.resolve(i);
	}
}

// This logs: Promise { 1 }, Promise { 2 }, Promise { 3 }
function forOfFoo() {
	for (let value of foo()) {
		console.log(value);
	}
}
forOfFoo();

// This logs: 1, 2, 3
async function forAwaitOfFoo() {
	for await (let value of foo()) {
		console.log(value);
	}
}
forAwaitOfFoo();

@bakkot
Copy link
Contributor Author

bakkot commented Nov 19, 2020

To demonstrate what @ExE-Boss is saying, consider

function* g(){
  yield 0; yield Promise.resolve(1);
}

(async function() {
  for await (let value of g()) {
    console.log(value);
  }
})();

This prints 0, 1.

But if instead we bypassed AsyncFromSyncIIterator by doing

(async function() {
  for await (let value of { [Symbol.asyncIterator]: g }) {
    console.log(value);
  }
})();

we would get 0, Promise { 1 }.

Presumably that's why this exists. I'll go ahead and close this.

@bakkot bakkot closed this as completed Nov 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants