javascript break in for-await loop finish the generator - javascript

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()

Related

Running forEach on Object.entries does not return the same thing as a for loop

I am iterating over an object using a regular for loop and that works fine for me. But, I was trying to remove all for loops of my code in favor of array iteration instead and, for some reason I can't understand why when using forEach I get a different result.
Note: forEach here is from a module called p-iteration
https://www.npmjs.com/package/p-iteration
This works fine, it returns the correct values.
for await (const [key, value] of Object.entries(tatGroupedByRegion)) {
onTarget = 0;
notOnTarget = 0;
const cases = [];
await forEach(value, async email => {
if (!cases.includes(email.new_name)) {
cases.push(email.new_name);
isOnTarget(email);
}
});
backlogData[key].tatd1 = percentage(onTarget, notOnTarget);
tatd1Total.value += parseInt(percentage(onTarget, notOnTarget), 10);
if ((parseInt(percentage(onTarget, notOnTarget) !== 0), 10)) {
tatd1Total.count += 1;
}
}
This does not work,this part here backlogData[key].tatd1 = percentage(onTarget, notOnTarget), returns the same value over and over.
await forEach(Object.entries(tatGroupedByRegion), async ([key, value]) => {
onTarget = 0;
notOnTarget = 0;
const cases = [];
await forEach(value, async email => {
if (!cases.includes(email.new_name)) {
cases.push(email.new_name);
isOnTarget(email);
}
});
backlogData[key].tatd1 = percentage(onTarget, notOnTarget);
tatd1Total.value += parseInt(percentage(onTarget, notOnTarget), 10);
if ((parseInt(percentage(onTarget, notOnTarget) !== 0), 10)) {
tatd1Total.count += 1;
}
});
exports.forEach = async (array, callback, thisArg) => {
const promiseArray = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
const p = Promise.resolve(array[i]).then((currentValue) => {
return callback.call(thisArg || this, currentValue, i, array);
});
promiseArray.push(p);
}
}
await Promise.all(promiseArray);
};
This is the implementation of forEach that you're using. The callback receives this as the first argument, this can be a problem.

Javascript cancel async for loop

I found this code in a project:
const fn = async () => {
let x = 0;
for(let i = 0; i < 50; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
I want to be able to stop it mid way, so that if I call it again, it will start from scratch and discard the previous call results. To avoid making two sets of requests at the same time.
This should do:
let token;
const fn = async () => {
const my = token = Symbol();
let x = 0;
for(let i = 0; i < 50 && my == token; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
While there still can be some overlap between the calls, any previous loops will break their iteration as soon as the next fn() call is started.
You can use any technique of using an external flag variable to break the loop.
As a workaround you can try to use a custom Promise class (Live demo):
import CPromise from "c-promise2";
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function api(i) {
console.log(`Async API call [${i}]`);
await delay(100);
return {};
}
const fn = () =>
CPromise.from(function* () {
let x = 0;
for (let i = 0; i < 50; i++) {
const res = yield api.call(i);
if (res.someProp) x++;
}
return x;
});
const cancelablePromise = fn().then(
() => console.log("Done"),
(err) => console.log(`Fail: ${err}`) // Fail: CanceledError: canceled
);
setTimeout(() => {
cancelablePromise.cancel(); // abort the async sequence (loop) after 3500ms
}, 3500);

Recursive function to ensure the return length is 5

I'm fetching some data from an xml source, but I need to check that the length of the array (return value) is 5, sometimes the response serves data with less than 5 elements (it's random).
If the return value (colorArray) is 5, the promise resolves with the correct array. Otherwise, if the function re-runs the promise resolves with undefined.
Appreciate any help in understanding why I'm getting undefined when colorArray.length is less than 5, or if anyone has any better suggestions about how I should run the code.
Thanks.
const runAxios = async () => {
console.log("function");
const res = await axios.get("/api/palettes/random");
let parser = new DOMParser();
let xml = parser.parseFromString(res.data, "application/xml");
let colors = xml.getElementsByTagName("hex");
const colorArray = [];
for (let i = 0; i < colors.length; i++) {
let colorList = colors[i].firstChild.nodeValue;
colorArray.push(colorList);
}
if (colorArray.length === 5) return colorArray;
else runAxios();
};
const result = runAxios();
result.then(e => {
console.log(e);
});
The problem is that you never returned runAxios:
const runAxios = async () => {
console.log("function");
const res = await axios.get("/api/palettes/random");
let parser = new DOMParser();
let xml = parser.parseFromString(res.data, "application/xml");
let colors = xml.getElementsByTagName("hex");
const colorArray = [];
for (let i = 0; i < colors.length; i++) {
let colorList = colors[i].firstChild.nodeValue;
colorArray.push(colorList);
}
if (colorArray.length === 5) return colorArray;
else return runAxios(); // <----------------------------------This
};
const result = runAxios();
result.then(e => {
console.log(e);
});
Also, depending on your requirements, I would suggest a do-while loop:
const runAxios = async () => {
do {
console.log("function");
const res = await axios.get("/api/palettes/random");
let parser = new DOMParser();
let xml = parser.parseFromString(res.data, "application/xml");
let colors = xml.getElementsByTagName("hex");
const colorArray = [];
for (let i = 0; i < colors.length; i++) {
let colorList = colors[i].firstChild.nodeValue;
colorArray.push(colorList);
}
} while(colorArray.length != 5);
return colorArray;
};
const result = runAxios();
result.then(e => {
console.log(e);
});

Why do the last two functions work, but not the first?

I have three different functions that should do the same thing, populate an array with resolved Promises, but it's not working for the first example.
Here's my code:
(async() => {
const items = [];
const someFn = async() => {
const v = await Promise.resolve(10);
items.push(Math.random());
return Promise.resolve(v * 10);
}
const arr = [];
for (let i = 0; i < 10; i++) {
arr.push(someFn);
}
await Promise.all(arr);
console.log("item 1", items);
})();
(async() => {
const items = [];
const someFn = async() => {
const v = await Promise.resolve(10);
items.push(Math.random());
return Promise.resolve(v * 10);
}
const arr = [...Array(10).keys()].map(someFn)
await Promise.all(arr);
console.log("items 2", items);
})();
(async() => {
const items = [];
const someFn = async() => {
const v = await Promise.resolve(10);
items.push(Math.random());
return Promise.resolve(v * 10);
}
for (let i = 0; i < 10; i++) {
await someFn();
}
console.log("items 3", items);
})()
This is the output:
item 1 []
items 2 [ 0.7450904427103939,
0.37106667256699555,
0.12035280341441346,
0.265221052932904,
0.7775494303685422,
0.4872532010723445,
0.6497680191919464,
0.2570485072009576,
0.5613137531648884,
0.95109416178435 ]
items 3 [ 0.25328649499657585,
0.5452758396760038,
0.7274346878509064,
0.9306670111476503,
0.22942578229725785,
0.32547900377461625,
0.9722902638678983,
0.9964743517593542,
0.2828162584401659,
0.7672256760378469 ]
Notice how item 1 is an empty array.
That's because in the first example, someFn is never executed:
for (let i = 0; i < 10; i++) {
arr.push(someFn);
}
await Promise.all(arr);
This part just pushes functions into the arr variable, it doesn't run them, thus not creating Promises and never filling the items array.
On the other hand, the other examples run the function someFn:
const arr = [...Array(10).keys()].map(someFn)
This fills the arr array with 10 executions of someFn (map executes them with the current value (0-9), the index (also 0-9) and the array itself).
for (let i = 0; i < 10; i++) {
await someFn();
}
And this obviously runs someFn in a loop.
To make the first example work, push the result of the function into the array:
(async () => {
const items = [];
const someFn = async () => {
const v = await Promise.resolve(10);
items.push(Math.random());
return Promise.resolve(v * 10);
}
const arr = [];
for (let i = 0; i < 10; i++) {
arr.push(someFn()); // <-- () added
}
await Promise.all(arr);
console.log("item 1", items);
})();
you’re pushing someFn but you want someFn(). note we’re calling the function.

Distribute iterator items over subiterators

I am playing with iterators in javascript. I found a way of chaining them so I don't have to nest them, just for readability.
It kind of works exactly like an array map chain.
What I like to do is distribute the items of the generator over a few sub iterators, catch the results and feed them to the next iterator. Such that the outcome will be this:
4
6
8
7
9
11
Given this piece of code:
"use strict";
const _ = require('lodash');
let things = chain([
gen,
addOne,
distribute([
addOne,
addTwo,
addThree
]),
addOne
]);
for(let thing of things) {
console.log(thing);
}
//////////////// DEFENITIONS ////////////////////
function* gen() {
yield* [1, 2, 3, 4, 5, 6];
}
function* addOne(iterator) {
for(let item of iterator) {
yield (item + 1)
}
}
function* addTwo(iterator) {
for(let item of iterator) {
yield (item + 2)
}
}
function* addThree(iterator) {
for(let item of iterator) {
yield (item + 3)
}
}
const distribute = _.curry(function* (iterators, iterator) {
// magic
});
function chain(iterators) {
return iterators.reduce((prevIterator, thisIterator, index) => {
if(index === 0) {
return thisIterator();
}
return thisIterator(prevIterator);
}, null);
}
Eventually i would like to add a distribution function to the distribution iterator, so it can determine which item to pass to which subiterator. For now it is just based on the order.
Question: How do I write a distribution iterator that takes a few subiterators as an argument and passes the results through to the next iterator.
It seems overly complex, but it works
const distribute = _.curry(function* (iterators, mainIterator) {
let iteratorIndex = 0;
let done = [];
for(let iterator of iterators) {
iterators[iteratorIndex] = iterator(mainIterator);
done.push(false);
iteratorIndex++;
}
while(true) {
let iteratorIndex = 0;
for(let iterator of iterators) {
let next = iterator.next();
done[iteratorIndex] = next.done;
if(!next.done) {
yield next.value;
}
iteratorIndex++;
}
if(done.every(done => done)) {
return;
}
}
});
And finally with a distribution function:
const distribute = _.curry(function* (genIteratorIndex, iterators, mainIterator) {
let iteratorIndex = 0;
let done = [];
// instantiate iterators
for(let iterator of iterators) {
iterators[iteratorIndex] = iterator(mainIterator);
done.push(false);
iteratorIndex++;
}
// Pass stuff through
while(true) {
let next = iterators[genIteratorIndex.next().value].next();
done[iteratorIndex] = next.done;
if(!next.done) {
yield next.value;
}
if(done.every(done => done === true)) {
return;
}
}
});
function* subSequent(len) {
let curr = 0;
while(true) {
if(curr === len) {
curr = 0;
}
yield curr;
curr++;
}
}
let things = chain([
gen,
addOne,
distribute(subSequent(3), [
addOne,
addTwo,
addThree
]),
addOne
]);

Categories

Resources