In a browser extension I'm trying to:
find a button
update its text every second for 10 seconds
invoke submit
Simple enough, but unfortunately I'm a JavaScript novice.
I'm clueless: Why does the code below not reach line #15 (after await)?
const Timeout = 10000;
const CountdownStep = 1000;
async function scheduleSubmit(node, timeout) {
originalTextContent = node.textContent;
while (timeout > 0) {
console.log(`Timeout: ${timeout}`);
try {
await new Promise((resolve => setTimeout(() => {
console.log(`[Promise] Timeout: ${timeout}`);
node.textContent = `${originalTextContent} (${timeout / 1000})`;
timeout -= CountdownStep;
console.log(`[Promise] Timeout: ${timeout}`);
}, CountdownStep)));
console.log('Hello? Helloooooooo??');
} catch (err) {
log(`Error: ${err}`);
}
}
node.submit();
}
scheduleSubmit(document.getElementById('foo'), Timeout);
<html><body>
<button type="button" id="foo">Run</button>
</body></html>
If you extract a simple delay helper that returns a Promise that resolves after the specified amount of time you code becomes way more readable. Not to speak you can test and debug your code separately to identify the issue.
const SUBMIT_TIMEOUT = 10000;
const COUNT_DOWN_STEP = 1000;
function delay(timeout) {
return new Promise(resolve => setTimeout(resolve, timeout))
}
async function scheduleSubmit(node, timeout) {
const originalTextContent = node.textContent;
while (timeout > 0) {
try {
await delay(COUNT_DOWN_STEP);
node.textContent = `${originalTextContent} (${timeout / 1000})`;
timeout -= COUNT_DOWN_STEP;
} catch (err) {
log(`Error: ${err}`);
}
}
node.submit();
}
scheduleSubmit(document.getElementById('foo'), SUBMIT_TIMEOUT);
<html><body>
<button type="button" id="foo">Run</button>
</body></html>
You just need to resolve the Promise calling resolve():
const Timeout = 10000;
const CountdownStep = 1000;
async function scheduleSubmit(node, timeout) {
originalTextContent = node.textContent;
while (timeout > 0) {
console.log(`Timeout: ${timeout}`);
try {
await new Promise((resolve => setTimeout(() => {
console.log(`[Promise] Timeout: ${timeout}`);
node.textContent = `${originalTextContent} (${timeout / 1000})`;
timeout -= CountdownStep;
console.log(`[Promise] Timeout: ${timeout}`);
resolve();
}, CountdownStep)));
console.log('Hello? Helloooooooo??');
} catch (err) {
log(`Error: ${err}`);
}
}
node.submit();
}
scheduleSubmit(document.getElementById('foo'), Timeout);
<html><body>
<button type="button" id="foo">Run</button>
</body></html>
Docs: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise
Related
The program runs a loop continuously until I press a button that pauses the loop. When I press the button a second time it should start running continuously. What happens now is on the first press it stops. Then the second press runs the loop once and stops again. When I press a third time, however, it does start running continuously again. How do I fix this?
const timer = ms => new Promise(res => setTimeout(res, ms));
async function program_loop() {
for (word_iterator = 0; word_iterator < some_length; word_iterator++) {
if (stop == true) await pauser();
//mini example
word.innerHTML = text_words[word_iterator]
await timer(200);
}
}
function pauser() {
return new Promise(resolve => {
let playbuttonclick = function () {
if (stop == false) {
stop = true;
pausebutton.innerHTML = 'Start'
}
else if (stop == true) {
stop = false;
pausebutton.innerHTML = 'Stop'
}
resolve('resolved')
}
pausebutton.addEventListener('click', playbuttonclick)
})
}
I've tried removing the if (stop==true) await pauser() but this ruins my whole program. Since then I need to press the button to advance the loop.
Each time you call pauser() , you add a new button click handler. Therefore, after several cycles, several handlers are triggered at once. Install the handler once or remove the old one each time.
Try this example:
const pausebutton = document.getElementById('pauseButton');
let stop = false;
const some_length = 500;
let promiseResolve;
const handleToggleStop = () => {
stop = !stop;
promiseResolve && promiseResolve();
};
pausebutton.addEventListener('click', handleToggleStop);
const timer = ms => new Promise(res => setTimeout(res, ms));
async function program_loop() {
for (word_iterator = 0; word_iterator < some_length; word_iterator++) {
if (stop) {
await new Promise((resolve) => {
promiseResolve = resolve;
});
}
//mini example
await timer(1000);
}
}
program_loop();
You don't really need a pauser function. You can do as follows;
let timer = ms => new Promise(res => setTimeout(res, ms)),
word = document.getElementById("word"),
bttn = document.getElementById("pause-button"),
prms = Promise.resolve(),
rslv;
async function program_loop() {
for (let word_iterator = 0; word_iterator < 1e6; word_iterator++) {
await prms;
word.innerText = word_iterator;
await timer(200);
}
}
bttn.addEventListener("click", _e => {
if (rslv) {
rslv();
rslv = null;
bttn.innerText = "STOP";
} else {
prms = new Promise(v => rslv = v);
bttn.innerText = "START";
}
});
program_loop();
<button id="pause-button">STOP</button>
<div id="word"></div>
There's something about chaining promises that I don't understand. The Node.js snippet below produces the following output. Why is promise.allSettled called after the first sleep on line 18 and not after the 2nd one on line 21?
Cycle 0 is going to sleep promise.js:2
Cycle 0 slept for 2 seconds promise.js:6
Returned from sleep function promise.js:19
Cycle 0 is going to sleep promise.js:2
Done with the process promise.js:27
Cycle 0 slept for 2 seconds promise.js:6
function sleep(cycle) {
console.log(`Cycle ${ cycle } is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${ cycle } slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 1;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i).then(() => {
console.log('Returned from sleep function');
sleep(i);
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
Because you haven't resolved the promise created sleep(i).then to the promise from the second sleep, so it doesn't wait for that second operation to finish before settling. You need a return in front of the second call:
function sleep(cycle) {
console.log(`Cycle ${ cycle } is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${ cycle } slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 1;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i).then(() => {
console.log('Returned from sleep function');
return sleep(i); // <============================
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
Here's a version with two cycles, and with the sleep calls more clearly marked:
function sleep(cycle, sub) {
console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i, "a").then(() => {
console.log(`Returned from sleep function (${i})`);
return sleep(i, "b"); // <============================
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
It might be clearer to split that sleep, sleep serial operation off into its own function, perhaps an async function:
async function twoSleep(i) {
await sleep(i, "a");
console.log(`Returned from sleep function (${i})`);
await sleep(i, "b");
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(twoSleep(i));
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
function sleep(cycle, sub) {
console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
resolve();
}, 2000);
});
}
async function twoSleep(i) {
await sleep(i, "a");
console.log(`Returned from sleep function (${i})`);
await sleep(i, "b");
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(twoSleep(i));
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
The issue is that Promise.allSettled runs all promises in parallel, so all of them resolve at the same time. If you want to run them one by one try
async function process() {
const cycles = 1;
for (let i = 0; i < cycles; i++) {
await sleep(i).then(() => {
console.log("Returned from sleep function");
return sleep(i);
});
}
console.log("Done with the process");
}
process();
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
[enter image description here][1]I have following code
function fnIsOnScreen(img, repeats = 5, desc, wait = 2000) {
let iCounter = 0;
return new Promise((resolve, reject) => {
console.log("Click is running");
var interval = setInterval(() => {
client.screenshot().then((data) => {
let buf = new Buffer(data.value, 'base64');
let img1 = cv.imdecode(buf)
result = img1.matchTemplate(img, 5).minMaxLoc();
if (result.maxVal >= 0.65) {
clearInterval(interval);
console.log("Object found #" + iCounter + " " + desc);
resolve(result);
} else {
console.log("Cant see object yet #" + iCounter);
iCounter++;
if (iCounter === repeats) {
clearInterval(interval);
let err = new Error("Object not found : " + desc);
throw err;
console.log(err);
return Promise.reject(err);
}
}
})
.catch ((err) => {
console.log(err);
})
}, wait);
}).catch ((err) => {
console.log(err);
});
}
Why am I not able to clear that interval? I call this function few times in a row. It looks like it clears it after the function is called next time but that does not help me much.
https://ctrlv.cz/shots/2018/01/10/kRmO.png
Sorry guys :) I am lil busy here. the if statement works
Several things jump out:
You never clear the interval in the catch on client.screenshot()'s promise. Presumably you want to do the same iCounter check there that you do in the else in then.
You leave the promise you've created via new Promise unresolved; returning return Promise.reject(err); from the then handler on client.screenshot()'s promise does nothing to settle the new promise you've created, as you aren't chaining.
result is undeclared in the quoted code.
I would strongly recommend not using setInterval to retry an asynchronous operation. It creates a race condition between the operation and the timer.
Here's a solution that attempts retry when the screenshot fails without using a timer at all. Note that I've broken the problem into specific parts: A reusable timeout function, a function that attempts the screenshot once, and a function that does retries as necessary.
function timeout(delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
function fnIsOnScreenOnce(img, desc, iCounter) {
return client.screenshot()
.then((data) => {
let buf = new Buffer(data.value, 'base64');
let img1 = cv.imdecode(buf)
let result = img1.matchTemplate(img, 5).minMaxLoc();
if (result.maxVal < 0.65) {
// Fail
const msg = "Can't see object yet";
throw new Error(iCounter === undefined ? msg : msg + " #" + iCounter);
}
// All good
return result;
});
}
function fnIsOnScreen(img, repeats = 5, desc, wait = 2000) {
let iCounter = 0;
const attempt = () => fnIsOnScreenOnce(img, desc, iCounter).catch(err => {
console.log(err.message);
iCounter++;
if (iCounter === repeats) {
// Failed, out of retries
throw new Error("Object not found : " + desc);
}
// Retry after waiting
return timeout(wait).then(attempt);
});
return attempt();
}
Live Example with some parts replaced with stubs; it uses some randomness to simulate the screenshot failing or returning maxVal above or below the cutoff, so you'll want to run it multiple times to see all the scenarios:
const client = {
screenshot() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random () < 0.5) {
const maxVal = Math.random();
console.log("resolving with " + maxVal);
resolve({maxVal});
} else {
console.log("rejecting");
reject(new Error("screenshot failed"));
}
}, 10);
});
}
};
function timeout(delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
function fnIsOnScreenOnce(img, desc, iCounter) {
return client.screenshot()
.then((data) => {
/*
let buf = new Buffer(data.value, 'base64');
let img1 = cv.imdecode(buf)
let result = img1.matchTemplate(img, 5).minMaxLoc();
*/
let result = data; // Stand in for the above
if (result.maxVal < 0.65) {
// Fail
const msg = "Can't see object yet";
throw new Error(iCounter === undefined ? msg : msg + " #" + iCounter);
}
// All good
return result;
});
}
function fnIsOnScreen(img, repeats = 5, desc, wait = 2000) {
let iCounter = 0;
const attempt = () => fnIsOnScreenOnce(img, desc, iCounter).catch(err => {
console.log(err.message);
iCounter++;
if (iCounter === repeats) {
// Failed, out of retries
throw new Error("Object not found : " + desc);
}
// Retry after waiting
return timeout(wait).then(attempt);
});
return attempt();
}
fnIsOnScreen("img", 5, "desc", 100)
.then(result => { console.log("result", result); })
.catch(err => { console.error("err", err.message); });
.as-console-wrapper {
max-height: 100% !important;
}
create an array to save intervals and clear it one by one.the intervalArray gets all intervals and clear it ones.
function fnIsOnScreen(img, repeats = 5, desc, wait = 2000) {
let iCounter = 0;
let intervalArray = [];
return new Promise((resolve, reject) => {
console.log("Click is running");
var interval = setInterval(() => {
client.screenshot().then((data) => {
let buf = new Buffer(data.value, 'base64');
let img1 = cv.imdecode(buf)
result = img1.matchTemplate(img, 5).minMaxLoc();
if (result.maxVal >= 0.65) {
intervalArray.push(interval)
clearintro(intervalArray);
console.log("Object found #" + iCounter + " " + desc);
resolve(result);
} else {
console.log("Cant see object yet #" + iCounter);
iCounter++;
if (iCounter === repeats) {
intervalArray.push(interval)
clearintro(intervalArray)
let err = new Error("Object not found : " + desc);
throw err;
console.log(err);
return Promise.reject(err);
}
}
})
.catch ((err) => {
console.log(err);
})
}, wait);
}).catch ((err) => {
console.log(err);
});
}
function clearintro(intervalArr) {
intervalArr.map(item => {
clearInterval(item);
})
}
You are not able to clear interval because you have defined interval variable inside of function make it globally then you will be able to clear it outside and inside anywhere of function
var interval ="";
function fnIsOnScreen(img, repeats = 5, desc, wait = 2000) {
let iCounter = 0;
return new Promise((resolve, reject) => {
console.log("Click is running");
interval = setInterval(() => {
client.screenshot().then((data) => {
let buf = new Buffer(data.value, 'base64');
let img1 = cv.imdecode(buf)
result = img1.matchTemplate(img, 5).minMaxLoc();
if (result.maxVal >= 0.65) {
clearInterval(interval);
console.log("Object found #" + iCounter + " " + desc);
resolve(result);
} else {
console.log("Cant see object yet #" + iCounter);
iCounter++;
if (iCounter === repeats) {
clearInterval(interval);
let err = new Error("Object not found : " + desc);
throw err;
console.log(err);
return Promise.reject(err);
}
}
})
.catch((err) => {
console.log(err);
})
}, wait);
}).catch((err) => {
console.log(err);
})
}
clear interval outside of function
clearInterval(interval);
I am struggling to make a simple waiting function in my program. I want to use promises and async await if possible. What I have so far:
function waitForCondition(conditionObj) {
var start_time = new Date().getTime()
function checkFlag() {
if (conditionObj.arg == conditionObj.test) {
console.log('met');
return new Promise(resolve => setTimeout(resolve, 1));
} else if (new Date() > start_time + 3000) {
console.log('not met, time out');
return new Promise(resolve => setTimeout(resolve, 1));
} else {
window.setTimeout(checkFlag, 1000);
}
}
checkFlag();
}
async function run() {
console.log('before');
await waitForCondition({arg: '1', test: '1'})
console.log('after');
}
run();
It should check every 1 second for a maximum time of 3 seconds. The console should look like this:
'before'
'met'
'after'
You have to return a promise:
function waitForCondition(conditionObj) {
return new Promise(resolve => {
var start_time = Date.now();
function checkFlag() {
if (conditionObj.arg == conditionObj.test) {
console.log('met');
resolve();
} else if (Date.now() > start_time + 3000) {
console.log('not met, time out');
resolve();
} else {
window.setTimeout(checkFlag, 1000);
}
}
checkFlag();
});
}
async function run() {
console.log('before');
await waitForCondition({arg: '1', test: '1'})
console.log('after');
}
run();
I refactored your code a bit. To get the current time, use Date.now(). And you should be OK with calling resolve without a timeout of 1 millisecond.
I believe what you are really looking for is
function waitForCondition(conditionObj) {
var start_time = new Date().getTime()
async function checkFlag() {
if (conditionObj.arg == conditionObj.test) {
console.log('met');
// return something?
} else if (new Date() > start_time + 3000) {
console.log('not met, time out');
// throw some error?
} else {
await new Promise(resolve => setTimeout(resolve, 1000));
return checkFlag();
}
}
return checkFlag();
}
or with a loop instead of the recursion
async function waitForCondition(conditionObj) {
var start_time = new Date().getTime()
while (true) {
if (conditionObj.arg == conditionObj.test) {
console.log('met');
break; // or return
}
if (new Date() > start_time + 3000) {
console.log('not met, time out');
break; // or throw
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
Your function waitForCondition implicitely returns undefined. try to add a return statment at the end of it:
return checkFlag();
And as mentionned in comment, there one condition where checkFlag() returns undefined that you might want to handle by returning a Promise.
I have a promise-returning function that does some async stuff, let's call it functionToRepeat().
I am trying to write the function repeatFunction(amount) , so that it will start the promise, wait for completion, start it again, wait for completion, and so on a given amount of times. This repeatFunction(amount) should also be thenable, so that I can chain other stuff after it's been executed.
Here is my attempt:
function functionToRepeat(){
let action = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
return action
}
function repeatFunction(amount) {
if(amount==0){
return Promise.resolve();
}
return functionToRepeat().then(function(){
repeatFunction(amount-1);
});
}
repeatFunction(5).then(function(){
console.log("DONE!");
})
This successfully chains my promises (or so it seams, I get one "resolved!" per second in the console).
However the .then() I try to chain after my repeatFunction(5) happens after the 1st promise ends, not after all 5 have ended!
So in my console I get:
resolved!
DONE!
resolved!
resolved!
resolved!
resolved!
What am I doing wrong and what should I change?
How about simply:
function repeat(func, times) {
var promise = Promise.resolve();
while (times-- > 0) promise = promise.then(func);
return promise;
}
When tested with this:
function oneSecond() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("tick");
resolve();
}, 1000);
});
}
repeat(oneSecond, 5).then(function () {
console.log("done");
});
this output is produced over 5 seconds:
tick
tick
tick
tick
tick
done
I think you are almost there, but you have to return the repeatFunction again in the then block of your function to repeat.
return functionToRepeat().then(function(){
return repeatFunction(amount-1);
});
}
If you have a then, but do not return anything, then it will just resolve the upper promise. That is what happened.
You're missing a return when you call repeatFunction(amount-1)
function functionToRepeat(){
let action = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
return action
}
function repeatFunction(amount) {
if(amount==0){
return Promise.resolve();
}
return functionToRepeat().then(function(){
return repeatFunction(amount-1); // Added return
});
}
repeatFunction(5).then(function(){
console.log("DONE!");
})
https://plnkr.co/edit/93T6B4QkBv0mYS4xPw0a?p=preview
You could use async/await with a simple while loop. This keeps you in Asyncland allows you to continue chaining after the repeated function is done
async function asyncRepeat (f, n) {
while (n-- > 0)
await f()
}
asyncRepeat(functionToRepeat, 5).then(() => {
console.log('done')
})
// some value
// some value
// some value
// some value
// some value
// done
This is trash tho. You're restricted to using a side-effecting function as your f argument to asyncRepeat. So logging to the console works, but what if you actually wanted to do something with that value?
Here's an update to asyncRepeat that allows you to thread a value thru the repeated application of your input function (asyncDouble in this example)
(important changes in bold)
async function asyncRepeat (f, n, x) {
while (n-- > 0)
x = await f(x)
return x
}
function asyncDouble (x) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('x is currently: %d', x)
resolve(x * 2) // resolve an actual value
}, 1000)
})
}
asyncRepeat(asyncDouble, 5, 2).then(result => {
console.log('result: %d', result)
})
// x is currently: 2
// x is currently: 4
// x is currently: 8
// x is currently: 16
// x is currently: 32
// result: 64
I wanted something similar, so I wrote a generic function at (https://repl.it/#turlockmike/BriskLovableLinuxkernel)
function repeat(fn, times) {
if (times == 1) {
return fn()
} else {
return new Promise(function(resolve, reject) {
return fn().then(function() {
return resolve(repeat(fn,times - 1))
})
})
}
}
Usage
function doSomething() {
return new Promise(function(resolve, reject) {
//do something interested here
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
}
repeat(doSomething, 5).then(() => {
console.log("all Done!")
})
const http = require('http');
const https = require('https');
const { t, d, r } = require('minimist')(process.argv.slice(2));
const checkRoot = config => {
const { delaySeconds, rootUrl } = config ? config : { delaySeconds: 6 };
const delay = delaySeconds * 1000;
const protocolString = rootUrl.split(':')[0];
const protocol = {
http: http,
https: https,
};
return new Promise(res => {
setTimeout(() => {
protocol[protocolString]
.get(rootUrl, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
res({ success: data.includes('<!doctype html>') });
});
})
.on('error', err => {
console.log(`Error: ${err.message}`);
res({ success: false });
});
}, delay);
});
};
const repeatChecking = async ({ times, delaySeconds, rootUrl }) => {
let isReady = false;
console.log(
`will try ${times}, and with ${delaySeconds} seconds delay in between for ${rootUrl}`
);
let i = 1;
while (i <= times) {
if (isReady === true) {
break;
}
const res = await checkRoot({ delaySeconds, rootUrl }).then();
isReady = res.success;
console.log(`testing ${i}, status: root ready => ${res.success}`);
i++;
}
if (isReady) {
console.log('Done, root is ready');
return;
}
process.stdout.write('ERROR: root could not be reached\n');
process.exit(1);
};
repeatChecking({ times: t, delaySeconds: d, rootUrl: r });
let loopP = (n, f, ...args) => {
let p = f(...args);
p.then(res => {
if (n - 1) {
loopP(n - 1, f, res);
}
});
};
Where n is the number of iterations, f is the Promise-returning function to be called. Each successive call to f is passed the results of the previous call when it resolves.
For instance...
let addOneP = i => {
console.log(i + 1);
return Promise.resolve(i + 1);
};
loopP(5, addOneP, 0);
// logs:
// 1
// 2
// 3
// 4
// 5
You might find relign useful for this kind of thing. Here's your example written with relign series and relign setTimeout.
const fnToRepeat = () =>
relign.setTimeout(() => console.log("resolved!"), 1000);
relign.series((new Array(5)).fill(fnToRepeat))
.then(() => console.log('done'));