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);
Related
let msg = "Done";
function promise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Done");
}, 2000);
});
}
async function func1() {
msg = "Pending";
console.log("Starting...");
const a = await promise();
console.log(a);
msg = a;
}
async function func2() {
console.log("Queued");
}
async function call() {
if ((msg) === "Done") {
func1();
} else {
func2();
}
}
<h1>PROMISE</h1>
<input onclick="call()" type="button" value="Click me " />
I added this piece of code into func2(), it runs func1() after the previous promise is resolved, but it also runs it immediately after click. How can i do so it only runs after previous promise is resolved.
func2() {
console.log("Queued");
await func1();
func1();
}
EDIT:
Guys! I solved this problem using Date().getTime() method, and adding "clicks" variable. The result is almost the same. But the way of doing it is different. When i click its immediately starts executing promise, but i wanted it to wait untill the promise from previous click is finished and only then start executing a new promise. I think there has to be some other simpler solution.
let msg = "Done";
let clicks = 0;
let t1;
let t2;
let timeout = 0;
let txt = "";
function promise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Done");
}, 2000 * clicks - timeout);
});
}
async function func1() {
clicks = 1;
timeout = 0;
txt = "";
msg = "Pending";
let time = new Date();
t1 = time.getTime();
const a = await promise();
let taym = new Date();
let now = taym.getTime();
createDiv(now, t1, txt);
msg = a;
}
async function func2() {
clicks++;
let time = new Date();
t2 = time.getTime();
timeout = t2 - t1;
const a = await promise();
let taym = new Date();
let now = taym.getTime();
txt = " and " + (now - t2) + " ms after last click";
createDiv(now, t1, txt);
}
async function call() {
if (msg === "Done") {
func1();
} else {
func2();
}
}
function createDiv(a, b, c) {
let div = document.createElement("div");
div.innerHTML = "Created " + (a - b) + " ms after main click" + c;
document.body.appendChild(div);
}
<h1>PROMISE</h1>
<input onclick="call()" type="button" value="Click me " />
Code below does what I think you want
resolving to the event, just to show that the timeStamp shown in the second click is the first click timeStamp, because that's what the promise resolves to in this example
Not sure how useful this code is though
const promise = new Promise(resolve => {
document.getElementById('bang').addEventListener('click', e => {
console.log('first click');
resolve(e);
}, { once: true });
})
promise.then(e => {
console.log(e.timeStamp, e.type, e.target.id);
document.getElementById('bang').addEventListener('click', e => {
promise.then(e => {
console.log('not the first click');
console.log(e.timeStamp, e.type, e.target.id);
})
})
})
<button id="bang">Resolve the promise</button>
I am learning promises in JS, I have tried somethings accidentally, but then I ended up with doubts. I have mentioned what I expected from code in //comments. The actual change on web page was - after 1 sec backgroundColor gets red but there is no change in color afterwards!!
const colorChange = (newColor) => {
return new Promise((resolve, reject) => {
resolve(newColor); // returns resolved promise without any delay
})
}
colorChange("red")
.then((newColor) => {
setTimeout(() => {
console.log('in 1st then ', newColor);
document.body.style.backgroundColor = newColor;
return colorChange("yellow"); // I think this will return a resolved promise after (1 sec from start)
}, 1000);
})
.then((newColor) => { // I think after (1 sec from start) this will get executed
setTimeout(() => {
console.log('in 2nd then ', newColor); // I thought that this should be yellow, but it is undefined!! why?
document.body.style.backgroundColor = newColor; // I think this will get executed after (2 sec from start)
},1000);
})
Console
in 1st then red
in 2nd then undefined
Can someone explain how this code ran?
Promise and setTimeout are asynchronous, returning something inside their callback will not affect outer function's return as it's execution is already completed.
You can try like this if you want, resolving promise based on timer.
const colorChange = (newColor, resolveAfter) => {
return new Promise((resolve, reject) => {
if (resolveAfter) {
setTimeout(() => resolve(newColor), resolveAfter);
} else {
resolve(newColor); // returns resolved promise without any delay
}
})
}
colorChange("red")
.then((newColor) => {
console.log('in 1st then ', newColor);
document.body.style.backgroundColor = newColor;
//this will return a resolved promise after (1 sec from start)
return colorChange("yellow", 1000);
})
.then((newColor) => { // after 1 sec from start this will get executed
document.body.style.backgroundColor = newColor;
})
new Promise(function(resolve, reject) {
setTimeout(() => resolve("red"), 1000); // (*)
}).then(function(result) { // (**)
console.log('in 1st then ', result);
document.body.style.backgroundColor = result;
result = "yellow"
return result ;
}).then(function(result) { // (***)
console.log('in 2second then ', result);
document.body.style.backgroundColor = result;
result= "blue"
return result ;
}).then(function(result) {
console.log('in 3th then ', result);
document.body.style.backgroundColor = result;
result= "red"
return result ;
});
var message = "I like apple and sunny and crazy bannaas ... and lovey cats";
var getMsgOneCharATime = function(cb, idx) {
// Fetches a character from the message. Simulates an API call by adding a delay to the request.
const apiDelay = 10;
setTimeout(function() {
cb(message[idx]);
}, apiDelay);
};
//// only after following line I can edit!
var countDistinctAsyncWords = function() {
const messageArray = [];
let currentIndex = 0;
function getPromisedCounter(value) {
return new Promise((resolve, reject) => {
if (!value) {
resolve({
value,
index
});
} else {
reject('there is a error');
}
});
}
var counter = setInterval(() => {
getMsgOneCharATime(getPromisedCounter, currentIndex);
currentIndex ++;
});
function saveWord(word) {
if (!word) clearInterval(counter);
console.log(word);
messageArray.push(word);
}
console.log(messageArray);
return messageArray.join();
}
console.log('---');
console.log(countDistinctAsyncWords());
console.log('---end-');
The purpose is to use countDistinctAsyncWords to print out the message that got fetched by the fake timeout api getMsgOneCharATime calling. I still not able to come out an idea how should I intercept each character correctly, assuming that the message can be infinity and I do not know the length of it. I am also open to other solutions.
Thanks!
I have a function say myMainFunction that is called from a client, that in turn calls mypromisified function.
Scenario:
mypromisified function can fail intermittently and I need to call this function with a delay (at an exponential increase) until success or until max no of tries reached.
What I have so far
The following code illustrates my scenario and repeats itself until success, but it tries indefinitely and not until certain count is reached
// called once from the client
myMainFuntion();
function rejectDelay(delay, reason) {
// call main function at a delayed interval until success
// but would want to call this only a limited no of times
setTimeout(() => {
myMainFuntion(); // calling main function again here but with a delay
}, delay);
}
function myMainFuntion() {
var delay = 100;
var tries = 3;
tryAsync().catch(rejectDelay.bind(null, delay));
}
function tryAsync() {
return new Promise(function(resolve, reject) {
var rand = Math.random();
console.log(rand);
if (rand < 0.8) {
reject(rand);
} else {
resolve();
}
});
}
while loop inside the rejectDelay would certainly not work as the counter would increment even before the actual function inside setInterval is executed, so am unsure as to how to go about this? so...
I tried promisifying the setInterval something like this knowing it will fail :( as it doesnt decrement the counter, but not sure how to get it right either .
function rejectDelay(delay, maximumTries, reason) {
return new Promise(function (resolve, reject) {
console.log(tries + ' remaining');
if (--maximumTries > 0) {
setTimeout(function() {
foo();
}, 500);
}
});
}
function myMainFunction() {
var delay = 100;
var maximumTries = 3;
tryAsync().catch(rejectDelay.bind(null, delay, maximumTries));
}
Using a couple of helper functions I've used a lot, this becomes very easy
The "helpers"
Promise.wait = (time) => new Promise(resolve => setTimeout(resolve, time || 0));
Promise.retry = (cont, fn, delay) => fn().catch(err => cont > 0 ? Promise.wait(delay).then(() => Promise.retry(cont - 1, fn, delay)) : Promise.reject('failed'));
The code:
function myMainFuntion() {
var delay = 100;
var tries = 3;
Promise.retry(tries, tryAsync, delay);
}
ES5 versions of the helpers
Promise.wait = function (time) {
return new Promise(function (resolve) {
return setTimeout(resolve, time || 0);
});
};
Promise.retry = function (cont, fn, delay) {
return fn().catch(function (err) {
return cont > 0 ? Promise.wait(delay).then(function () {
return Promise.retry(cont - 1, fn, delay);
}) : Promise.reject('failed');
});
};
A slightly different approach that uses "asynchronous recursion" to call a nested function within a function that returns a promise:
function retry( func, maxTries, delay) {
var reTry = 0;
return new Promise( function(resolve, reject) {
function callFunc() {
try
{
func().then(resolve, function( reason) {
if( ++reTry >= maxTries) {
reject( reason);
}
else {
setTimeout( callFunc,
typeof delay=="function" ? delay( retry) : delay );
}
});
}
catch(e) {
reject(e);
}
}
callFunc();
});
}
// ******* run snippet to test ********
var retryCount = 0;
function getDelay( n) {
// return 100 * n*n + 500; // for example
++ retryCount;
return 100; // for testing
}
function testFunc() {
return Math.random() < 0.8 ? Promise.reject("too many tries") : Promise.resolve( "success");
}
retry( testFunc, 5, getDelay).then(
function(data) { console.log("data: %s, retryCount %s", data, retryCount);},
function(reason){console.log("reason: %s, retryCount %s", reason, retryCount);}
)
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'));