Javascript cancel async for loop - javascript

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

Related

How can I call a function when another have been executed

I'm a complete beginner at JavaScript. I just want to call the function called seconONe() just after the function firstOne() completes its execution. by saying this, I mean the function two will call when the value of that p1 is 4 ( in this case ); I can achieve it by calling a setTimeout() function. but what if I don't know how many does it take to execute { the first one() }?
// getting DOM element
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1.innerHTML = i;
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
A possible solution is to work with promises. More info about promises here.
Working example
var p1 = 1;
var p2 = 2;
var p3 = 3;
const firstPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1 = i;
}, i * 1000);
}
resolve()
});
const secondPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2 = i;
}, i * 1000);
}
resolve()
});
//run first promise
console.log("First promise called")
firstPromise
.then((response) => {
console.log("First promise done")
//run second promise after first promise succeed
console.log("Second promise called")
secondPromise
.then((response) => console.log("Second promise done"))
})
your question is not childish at all. What you will need to understand are callbacks and promise handlers. This just tells JavaScript to wait till a task has been completed in order to execute the next task.
firstOne().then(() => secondOne())
put if condition in your firstOne function.
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
if(i == 4){
seconOne();
}else{
p1.innerHTML = i;
}
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
Just to build on the other answers that have suggested using a Promise, here's a more generalised solution that also uses async/await.
(In summary: call a function with a count, and an element. That function will return a promise that "at some point" work will be completed. An inner function loops updating the element content until that count has been reached, at which point the promise resolves, and the next thing can start).
// Cache the elements
const p1 = document.querySelector('#one');
const p2 = document.querySelector('#two');
const p3 = document.querySelector('#three');
// `timer` accepts a count, and the element
// to apply the count to
function timer(count, el) {
// Return a promise that basically says:
// once I'm done doing this work, resolve,
// and then the event queue can
// get on with the next thing
return new Promise(resolve => {
// So we create a loop that logs the numbers
// in our element up to the count we specified.
// and when that number is reached, resolve the promise
function loop(n = 0) {
// If our current `n` value is <= count
if (n <= count) {
// Set the content of the element
el.textContent = n;
// Call `loop` again after a second
// with an incremented `n` value
setTimeout(loop, 1000, ++n);
// Otherwise resolve the promise
} else {
resolve();
}
}
loop();
});
}
// And now we just await each resolved promise
async function main() {
await timer(4, p1);
await timer(7, p2);
await timer(20, p3);
console.log('Done!');
}
main();
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
Additional documentation
querySelector

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

JavaScript Async/Await: Unsuccessful with attempts to modify working code

I've written a short script using Async/Await that prints out letters one by one after short intervals. Based on what I understood to be happening, I tried rewriting the code in several ways expecting the same result, but I have been unable to make any of these alternatives work. In particular, I thought it would be straightforward to change where in the code the console.log() happens.
Here's the original working code:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
// Async/Await with a For loop calling setTimeoutPromise()
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
sendMessage();
}
welcomeMessage();
Then I tried to make a few modifications, none of which worked.
Mdofication #1: In this version, I thought I could just call and run the code in the sendMessage() function directly without needing to call it later. However, nothing happened after this modification:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
}
welcomeMessage();
Modification #2: I reverted the code and then tried to move the console.log() function into the setTimeout() function thinking this would be called on every loop. Both with empty ()'s and with (resolve) being passed into setTimeout(), it only printed the first letter. With (resolve, num) it says undefined:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Modification #3: Finally, I tried to define a function in advance to be passed into setTimeout() which would be used to handle "resolve" and console.log(). I tried a few variations and again didn't seem to be progressing through the loop as console.log() was only called once.
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
It appears to me, that you've started working with asynchrony before you got a deep understanding of how synchronous JavaScript works. Asynchrony is hard enough on its own too, so combined to that, it made you completely confused.
Let me explain what's going on and what's wrong in your snippets.
Let's start with the working one.
That code:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
...creates a function named setTimeoutPromise, that:
takes an index (number) as its argument
returns a promise that:
after timer[num] milliseconds
resolves to undefined (setTimeout doesn't pass anything to its callback by default; in this case the callback is the resolve function)
The next part:
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...defines an async function named sendMessage, that:
iterates over message, for each character:
calls setTimeoutPromise and awaits the promise it returns
after waiting, logs the current character to the console
Finally,
sendMessage();
...calls sendMessage, and therefore initiates the typing.
Now, let's move on to the next snippet.
This code:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...creates an async function, but it doesn't call or assign it to any variable: simply discards it.
To fix this snippet, call the function immediately by putting () after it!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
(async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
})(); //The () at the end calls it
}
welcomeMessage();
Problematic snippet #2
There are 2 problems with this:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => { //Problem 1
console.log(message[num]);
resolve; //Problem 2
}, timer[num]);
})
};
You try to take an argument named resolve from setTimeout, but as I mentioned above, it doesn't pass any.
To solve it, remove resolve from setTimeout((resolve) => {! We already have the resolve function from the above line, because of lexical scope.
You don't call resolve, that keeps the awaiting code hanging after the first letter (the promise never gets resolved).
To fix it, put () after resolve!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(() => {
console.log(message[num]);
resolve();
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Problematic snippet #3
There are 2 problems in this code as well:
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func; //Problem 1
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]); //Problem 2
})
};
The same as above; newFunction doesn't call resolve (named fn).
Try to not forget the () when you intend to call a function
That's the opposite of Problem 1. You immediately call newFunction (due to the parentheses after it: (resolve, num)), and pass its return value (undefined) to the setTimeout. Without Problem 1, this would result in immediately logging all letters.
In this case, let setTimeout to call that function internally by removing (resolve, num) after it. To pass parameters to it, setTimeout accepts additional arguments, that it will hand over to its callback (in this case newFunction).
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
All together...
It is possible to combine these fixes, to get something like:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
(async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
})();
}
welcomeMessage();
Conclusion
Use parentheses (()) to call a function, but avoid them to use the function as an object: pass or assign it to something, get or set its properties, etc.

Var has same value after using await in function running using Promise.all

In a function run1 random value is generated & stored in random_var, await is used to delay and then random_value is resolved.
When running run1 asynchronously using Promise.all the random_var is changed when logging after await statement as shown in the code snippet demo
main()
async function main() {
await Promise.all([run1(), run1(), run1()]).then(value => {
console.log({ values: value })
})
}
async function run1() {
return new Promise(async (resolve, reject) => {
random_var = makeid(6)
console.log('Logging 1st time has different values', random_var)
await new Promise(resolve => setTimeout(resolve, 500))
console.log('Logging 2nd time has same values', random_var)
resolve(random_var)
})
}
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
This has to do with hoisting and scope.
You line random_var = makeid(6) doesn't use let or const so it gets hoisted to the global scope where it will not change when the promises resolve.
changing it to const random_var = makeid(6) (or let or var, but best practice is const here because you're not going to ever mutate it) should resolve the issue.
main()
async function main() {
await Promise.all([run1(), run1(), run1()]).then(value => {
console.log({ values: value })
})
}
async function run1() {
return new Promise(async (resolve, reject) => {
const random_var = makeid(6)
console.log('Logging 1st time has different values', random_var)
await new Promise(resolve => setTimeout(resolve, 500))
console.log('Logging 2nd time has same values', random_var)
resolve(random_var)
})
}
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

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.

Categories

Resources