Using javascript's Symbol.asyncIterator with for await of loop - javascript

I am trying to understand javascript's Symbol.asyncIterator and for await of. I wrote some simple code and it throws an error saying:
TypeError: undefined is not a function
on the line which tries to use for await (let x of a).
I could not understand the reason for it.
let a = {}
function test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
return Promise.resolve(`Greater than 5: (${i})`)
}else {
return Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) { // LINE THAT THROWS AN ERROR
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
I create an empty object a and insert a key Symbol.asyncIterator on the same object and assign it a function named test that returns a Promise. Then I use for await of loop to iterate over all the values that the function would return.
What am I doing incorrectly?
PS: I am on the Node version 10.13.0 and on the latest version of Chrome

To be a valid asyncIterator, your test function must return an object with a next method that returns a promise of a result object with value and done properties. (Technically, value is optional if its value would be undefined and done is optional if its value would be false, but...)
You can do that in a few ways:
Completely manually (awkward, particularly if you want the right prototype)
Half-manually (slightly less awkward, but still awkward to get the right prototype)
Using an async generator function (simplest)
You can do it completely manually (this doesn't try to get the right prototype):
function test() {
let i = -1;
return {
next() {
++i;
if (i >= 10) {
return Promise.resolve({
value: undefined,
done: true
});
}
return Promise.resolve({
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
});
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
You can do it half-manually writing a function that returns an object with an async next method (still doesn't try to get the right prototype):
function test() {
let i = -1;
return {
async next() {
++i;
if (i >= 10) {
return {
value: undefined,
done: true
};
}
return {
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
};
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Or you can just use an async generator function (easiest, and automatically gets the right prototype):
async function* test() {
for (let i = 0; i < 10; ++i) {
yield i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`;
}
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
About prototypes: All async iterators you get from the JavaScript runtime itself inherit from a prototype that provides the very basic feature of ensuring the iterator is also iterable (by having Symbol.iterator be a function returning this). There's no publicly-available identifer or property for that prototype, you have to jump through hoops to get it:
const asyncIteratorPrototype =
Object.getPrototypeOf(
Object.getPrototypeOf(
async function*(){}.prototype
)
);
Then you'd use that as the prototype of the object with the next method that you're returning:
return Object.assign(Object.create(asyncIteratorPrototype), {
next() {
// ...
}
});

The test function must not return a promise, but an Iterator (an object with a next() ) method, that method then has to return a Promise (which makes it an async iterator) and that Promise has to resolve to an object containing a value and a done key:
function test() {
return {
next() {
return Promise.resolve({ value: "test", done: false });
}
};
}
Now while that works, it is not that useful yet. You could however create the same behaviour with an async generator function:
async function* test() {
await Promise.resolve();
yield "test";
}
Or in your case:
async function* test() {
for(let i = 0; i < 10; i++) {
if(i > 5) {
await Promise.resolve();
yield `Greater than 5: (${i})`;
}else {
await Promise.resolve();
yield `Less than 5: (${i})`;
}
}
}

You should make test an async generator function instead, and yield instead of return:
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
yield Promise.resolve(`Greater than 5: (${i})`)
}else {
yield Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
It looks like the test function needs to be async so that the x in the for await gets unwrapped, even though test doesn't await anywhere, otherwise the x will be a Promise that resolves to the value, not the value itself.
yielding Promise.resolve inside an async generator is odd, though - unless you want the result to be a Promise (which would require an extra await inside the for await loop), it'll make more sense to await inside the async generator, and then yield the result.
const delay = ms => new Promise(res => setTimeout(res, ms));
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
await delay(500);
if(i > 5) {
yield `Greater than 5: (${i})`;
}else {
yield `Less than 5: (${i})`;
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
If you didn't make test a generator, test would have to return an iterator (an object with a value property and a next function).

Related

Generator next parameter in `for await`

When you have an async generator, you can call next(x) to return a result to the generator.
async function* generate() {
for (let i = start; i <= 5; i++) {
const result = yield i;
console.log("result", result)
}
}
;(async () => {
const generator = generate()
await generator.next(1)
await generator.next(2)
})();
This will print 1 and 2 as this is the next parameter that is passed as a result back to the generator.
How to replicate this same behaviour but using await for ?
for await (let value of generate()) {
// ... no way to send `next` parameter here
}

how to call async function in response

I need to use await in a response function, but I couldn't figure out how. (Note: func2() is an async function)
async function get_data() { 
for (i = 0; i < 10; i++) {
// http request, returns json...
await func1(data1, data2).then(json => {   
if (json.value < 100) {
await func2(); //error => await is only valid in async function
}  
}) 
}
}
I have to wait for func2, how can I do that?
I tried this:
async function get_data() { 
for (i = 0; i < 10; i++) {
await func1(data1, data2).then(json =>
async function() { // nothing happens, function doesnt work.
if (json.value < 100) {
await func2();   
}  
}) 
}
}
It's generally easier if you don't mix await and .then(). Since you want to sequence two operations and it appears you want the for loop to be sequenced too, it's simpler with just await.
async function get_data() {
 for (let i = 0; i < 10; i++) {
  let json = await func1(data1, data2);
  if (json.value < 100) {
    await func2();
  }
 }
}
You can get rid of .then() and use await instead.
await func1 and get the value and then check if the value satisfies the condition and if it does await func2.
Check the demo snippet below:
async function get_data() {
for (i = 0; i < 10; i++) {
const { value } = await func1("Hello", "World");
if (value < 100) {
console.log(await func2(value));
}
}
console.log("END!")
}
const func1 = (d1, d2) =>
new Promise((res) => setTimeout(() => res({ value: Math.random(0) * 200 }), 200));
const func2 = (v) => new Promise((res) => setTimeout(() => res(v), 200));
get_data();

javascript break in for-await loop finish the generator

I have written this code to iterate over github issues with a specific number (like pagination), in this case with 3 issues at once:
const getUrl = (page) => `https://api.github.com/repos/angular/angular/issues?page=${page}`;
const getIssues = async function*() {
for (let p = 1; true; p++) {
const url = getUrl(p);
const rawData = await fetch(url, {
headers: { 'User-Agent': 'app' }
});
const issues = await rawData.json();
for (let issue of issues) {
yield issue;
}
}
};
const generator = getIssues();
document.querySelector('[data-next]').addEventListener('click', async function() {
let i = 0;
for await (let issue of generator) {
console.log(issue);
if (++i === 3) break;
}
console.log(await generator.next());
});
The element with data-next attribute is a button. The expected behavior is every click on the button loads the next 3 issues. The problem is, the generator finished after the break (the console.log prints this: {value: undefined, done: true}).
Why it is finished, and how could I make this work as expected?
It's a known problem/feature, that for..of terminates the generator (see e.g. here). One possible solution is to provide a proxy which will persist the actual generator state in a closure:
function persist(gen) {
return {
next() {
return gen.next()
},
[Symbol.asyncIterator]() {
return this
},
[Symbol.iterator]() {
return this
}
}
}
//
const getUrl = (page) => `https://jsonplaceholder.typicode.com/posts/${page}/comments`;
const getIssues = async function* () {
for (let p = 1; true; p++) {
const url = getUrl(p)
const raw = await fetch(url)
const data = await raw.json()
yield* data
}
};
async function main() {
const generator = persist(getIssues());
let i = 0;
for await (let x of generator) {
console.log(i, x.postId, x.id, x.name);
if (++i === 4) break;
}
console.log('break'); i = 0;
for await (let x of generator) {
console.log(i, x.postId, x.id, x.name);
if (++i === 4) break;
}
console.log('break'); i = 0;
for await (let x of generator) {
console.log(i, x.postId, x.id, x.name);
if (++i === 4) break;
}
}
main()

Async/Await is not working as expected with for loop statement

I am new to Javascript and nodejs. While trying to understand how promises and callbacks work, i tried calling a function in a 'for' loop as shown below. What i was trying to achieve is to print 'i' value every 2 seconds using promises. However, the program waits for 2 seconds and prints i value 3 times and exits.
for(let i = 0; i < 3 ; i++){
func(callback,i);
}
async function func(callback,i){
await callback(i);
}
function callback(i){
return new Promise((res,rej) =>{
setTimeout(()=>{
console.log('i = ',i)
res();
}, 2000);
})
}
Can anybody please help me understand why this is happening?
You are pretty close. The missing information is async functions (your func()) implicitly returns an AsyncFunction object which implicitly returns a Promise itself too (doc).
Your code using immediately invoked function expression would be
(async () => {
for(let i = 0; i < 3 ; i++){
await func(callback,i);
}
})()
async function func(callback,i){
await callback(i);
}
function callback(i){
return new Promise((res,rej) =>{
setTimeout(()=>{
console.log('i = ',i)
res();
}, 2000);
})
}
Please note now in most of the browsers is natively available the construct for await..of (doc). You can try experimenting this one too.
You can just wrap your loop with async immediately executed function and add await within it (as was already suggested here):
(async () => {
for(let i = 0; i < 3 ; i++){
await callback(i);
}
})();
function callback(i){
return new Promise((res,rej) =>{
setTimeout(()=>{
console.log('i = ',i)
res();
}, 2000);
})
}
Here is my original answer before Bergi's edit:
(async () => {
for(let i = 0; i < 3 ; i++){
await func(callback,i);
}
})()
async function func(callback,i){
await callback(i);
}
function callback(i){
return new Promise((res,rej) =>{
setTimeout(()=>{
console.log('i = ',i)
res();
}, 2000);
})
}
You need to wait for async function to complete
for(let i = 0; i < 3 ; i++){
await func(callback,i);
}
But since you can't use await keyword in the global scope, you will need to wrap your for loop in an async function, and than call it
async function myTest(){
for(let i = 0; i < 3 ; i++){
await func(callback,i);
}
}
myTest()
Here are some functions which help you understand how promises work with Arrays, where we make common mistakes.
function promiseFunction(v) {
return new Promise((resolve) => {
setTimeout(() => resolve(v), 1000);
});
}
function f1() {
[1, 2, 3, 4]. forEach(async(i) => {
const v = await promiseFunction(i);
console.log(`-f1 v- ${v}<br/>`);
});
console.log('all done<br/>');
}
async function f2() {
await Promise.all([1, 2, 3, 4].map(async(i) => {
const v = await promiseFunction(i);
console.log(`-f2 v- ${v}<br/>`);
}));
console.log('all done<br/>');
}
async function f3() {
await [1, 2, 3, 4].reduce((p, i) => {
return p.then(async () => {
const v = await promiseFunction(i);
console.log(`-f3 v- ${v}<br/>`);
});
}, Promise.resolve());
console.log('all done<br/>');
}
async function func() {
console.log('f1');
await f1();
console.log('f2');
await f2();
console.log('f3');
await f3();
}
func();
f1() will print all done first and then print 1,2,3,4 at once after a second.
f2() will print 1,2,3,4 at once after a second then print all done.
f3() will print 1,2,3,4 in every second, and then print all done.
You async function func also returns a promise so you need the await keyword before its call.
for(let i = 0; i < 3 ; i++){
await func(callback,i);
}

Concise partial application of ES2018 Async Generator?

I have an async generator function defined, and want to create a second async generator function that is a partial application of the first function. For example, this works:
async function* fn1(a, b) {/* do something */}
async function* fn2() {
const a1 = fn1(0, 0);
for await (const value of a1) {
yield value;
}
}
My question is: is there a more concise way to define fn2?
fn1 returns the async iterable that fn2 is iterating over, so rather than iterating manually in fn2, you can just yield the iterable created from fn1 and let fn2's consumer handle it:
async function* fn2() {
yield* fn1(0, 0);
}
const delay = ms => new Promise(res => setTimeout(res, ms));
async function* fn1(a, b) {
for (let i = 0; i < 3; i++) {
await delay(1000);
yield i;
}
}
async function* fn2() {
yield* fn1(0, 0);
}
(async () => {
for await (const val of fn2()) {
console.log('Consumer got', val);
}
console.log('Done');
})();
// Original version, without yield* (just to illustrate equivalence):
const delay = ms => new Promise(res => setTimeout(res, ms));
async function* fn1(a, b) {
for (let i = 0; i < 3; i++) {
await delay(1000);
yield i;
}
}
async function* fn2() {
const a1 = fn1(0, 0);
for await (const value of a1) {
yield value;
}
}
(async () => {
for await (const val of fn2()) {
console.log('Consumer got', val);
}
console.log('Done');
})();
The most concise definition can be achieved by simply not making fn2 a generator function. Use a normal function that returns a generator - the generator created by the call to fn1:
function fn2() {
return fn1(0, 0);
}

Categories

Resources