I want to use requestAnimationFrame with Promise, so I can use it this way
opacityToggle("layername",0).then(i=>"do some stuff after animation ends")
, but not sure how to do this. Here is my code:
function opacityToggle(layerName, opacity) {
if (!layerName) return;
var requestID;
var s = 0;
return animate().then(i => i)
function animate() {
requestID = requestAnimationFrame(animate);
if (s < 1) {
s += 0.01
s = +s.toFixed(2)
console.log('s', layerName, s, opacity);
map.setPaintProperty(layerName, 'fill-opacity', s);
} else {
cancelAnimationFrame(requestID);
return new Promise(resolve => resolve)
}
}
}
While #Terry has the simple solution of wrapping just everything in the promise constructor, you get the real power of promises if you promisify only the requestAnimationFrame call itself:
async function opacityToggle(layerName, opacity) { /*
^^^^^ */
if (!layerName) return;
var s = 0;
while (s < 1) {
// ^^^^^
s += 0.01
s = +s.toFixed(2)
console.log('s', layerName, s, opacity);
map.setPaintProperty(layerName, 'fill-opacity', s);
await new Promise(resolve => {
// ^^^^^
requestAnimationFrame(resolve);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
});
}
}
If you can't use async/await and/or want to do it with the recursive approach, it would be
function opacityToggle(layerName, opacity) {
if (!layerName) return Promise.resolve();
var s = 0;
return animate();
function animate() {
if (s < 1) {
s += 0.01
s = +s.toFixed(2)
console.log('s', layerName, s, opacity);
map.setPaintProperty(layerName, 'fill-opacity', s);
return new Promise(resolve => {
// ^^^^^^
requestAnimationFrame(resolve);
}).then(animate);
// ^^^^^^^^^^^^^^
} else {
return Promise.resolve();
}
}
}
You're slightly over-complicating the return statement of your opacityToggle function. You simply return the promise and encapsulate all the RAF logic inside of it. This should be sufficient:
function opacityToggle(layerName, opacity) {
if (!layerName) return;
var requestID;
var s = 0;
return new Promise(resolve => {
// Animation steps
function animate() {
if (s < 1) {
s += 0.01
s = +s.toFixed(2)
console.log('s', layerName, s, opacity);
map.setPaintProperty(layerName, 'fill-opacity', s);
requestID = requestAnimationFrame(animate);
} else {
// If animation is complete, then we resolve the promise
cancelAnimationFrame(requestID);
resolve();
}
}
// Start animation
requestID = requestAnimationFrame(animate);
});
}
It is unclear in your question tho, of what value you want to resolve the promise with. You can simply provide it as resolve(yourResolvedValueHere) if you want to do so.
Related
I have an app that animates a value. Below, if to is set, the amount lerps to it.
const lerp = (v0, v1, t) => {
return (1 - t) * v0 + t * v1;
}
const app = {
to: false,
amount: 20,
animate(){
requestAnimationFrame(this.animate.bind(this));
if(this.to !== false){
this.amount = lerp(this.amount, this.to, 0.1)
if(Math.abs(this.amount - this.to) < 0.001){
this.amount = this.to;
this.to = false;
}
console.log(this.amount);
}
},
init(){
this.animate();
}
}
app.init();
console.log("Waiting to start");
setTimeout(() => {
console.log("Started!");
app.to = 0;
}, 1000)
This works great. But I'd like to call a function when it finishes the process, and that function may change. Ideally, I'd like to add it like so:
...
promise: null,
animate(){
requestAnimationFrame(this.animate.bind(this));
if(this.to !== false){
this.amount = lerp(this.amount, this.to, 0.1)
if(Math.abs(this.amount - this.to) < 0.001){
this.amount = this.to;
this.to = false;
// Resolve the promise so logic can continue elsewhere
if(this.promise) this.promise.resolve();
}
}
console.log(this.amount);
},
stop(){
this.promise = something... // Not sure what this should be
await this.promise;
// subsequent logic
nextFunction()
}
I can't get my head around how I can properly set this up. Any help welcome.
Wrapper entire function in promise and then resolve it
const lerp = (v0, v1, t) => {
return (1 - t) * v0 + t * v1;
}
const app = {
to: false,
amount: 20,
animate() {
return new Promise(resolve => {
const inner = () => {
requestAnimationFrame(inner.bind(this));
if (this.to !== false) {
this.amount = lerp(this.amount, this.to, 0.1)
if (Math.abs(this.amount - this.to) < 0.001) {
this.amount = this.to;
this.to = false;
resolve()
}
}
console.log(this.amount);
}
inner()
})
},
init() {
return this.animate();
}
}
app.init().then(() => alert('over'));
setTimeout(() => {
app.to = 0;
}, 1000)
In order to create a promise for an arbitrary action (rather than a promise for the whole animate loop like Konrad showed) I used something from this answer: https://stackoverflow.com/a/53373813/630203 to create a promise I could resolve from anywhere.
const createPromise = () => {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
const lerp = (v0, v1, t) => {
return (1 - t) * v0 + t * v1;
}
const app = {
to: false,
amount: 20,
animateToPromise: null,
animateToResolve: null,
animate(){
requestAnimationFrame(this.animate.bind(this));
if(this.to !== false){
this.amount = lerp(this.amount, this.to, 0.1)
if(Math.abs(this.amount - this.to) < 0.001){
this.amount = this.to;
this.to = false;
if(this.animateToPromise){
this.animateToResolve();
this.animateToPromise = null;
this.animateToResolve = null;
}
}
console.log(this.amount);
}
},
init(){
this.animate();
},
async animateTo(n){
const { promise: animateToPromise, resolve: animateToResolve } =
createPromise();
this.to = n;
this.animateToPromise = animateToPromise;
this.animateToResolve = animateToResolve;
await this.animateToPromise;
return true;
}
}
app.init();
console.log("Waiting to start");
(async () => {
setTimeout(async () => {
console.log("Started!");
await app.animateTo(0);
console.log("Got to 0!");
console.log("now for 20");
await app.animateTo(20);
console.log("done :)");
}, 1000)
})();
This means I can queue my promises with a single animate function running.
Quite new to coding in general and not used async/await much before.
I'm trying to get an enemy in a little maze game I'm making to move up, left, down, right, repeat.
Problem I have is that the function just executes everything at once instead of waiting. I know I'm missing something, but can't put my finger on it. Can anyone advise?
async function enemyMovement() {
enemyCSSVertical = -50;
enemyCSSHorizontal = 400;
enemyX = 9;
enemyY = 10;
await enemyUp();
await enemyLeft();
await enemyDown();
await enemyRight();
}
// move up
async function enemyUp() {
setTimeout(() => {
console.log("up");
enemyCSSVertical += -50;
enemy.css("top", enemyCSSVertical);
}, 1000);
}
//move left
async function enemyLeft() {
setTimeout(() => {
console.log("left");
enemyCSSHorizontal += -50;
enemy.css("left", enemyCSSHorizontal);
}, 1000);
}
async function enemyDown() {
setTimeout(() => {
console.log("down");
enemyCSSVertical += 50;
enemy.css("top", enemyCSSVertical);
}, 1000);
}
async function enemyRight() {
setTimeout(() => {
console.log("right");
enemyCSSHorizontal += 50;
enemy.css("left", enemyCSSHorizontal);
}, 1000);
}
You need to create a promise in your move functions and resolve it within the timeout
async function enemyUp() {
var p = new Promise(function(resolve,reject) {
setTimeout(() => {
console.log("up");
enemyCSSVertical += -50;
enemy.css("top", enemyCSSVertical);
resolve("Finished");
}, 1000);
}
return p;
}
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 want to have two functions (an animation downwards and animation upwards) executing one after the other in a loop having a timeout of a few seconds between both animations. But I don't know how to say it in JS …
Here what I have so far:
Function 1
// Play the Peek animation - downwards
function peekTile() {
var peekAnimation = WinJS.UI.Animation.createPeekAnimation([tile1, tile2]);
// Reposition tiles to their desired post-animation position
tile1.style.top = "-150px";
tile2.style.top = "-150px";
peekAnimation.execute();
}
Function 2
// Play the Peek animation - upwards
function unpeekTile() {
var peekAnimation = WinJS.UI.Animation.createPeekAnimation([tile1, tile2]);
// Reposition tiles to their desired post-animation position
tile1.style.top = "0px";
tile2.style.top = "0px";
peekAnimation.execute();
}
And here's a sketch how both functions should be executed:
var page = WinJS.UI.Pages.define("/html/updateTile.html", {
ready: function (element, options) {
peekTile();
[timeOut]
unpeekTile();
[timeOut]
peekTile();
[timeOut]
unpeekTile();
[timeOut]
and so on …
}
});
You can do this using setTimeout or setInterval, so a simple function to do what you want is:
function cycleWithDelay() {
var delay = arguments[arguments.length - 1],
functions = Array.prototype.slice.call(arguments, 0, arguments.length - 1),
pos = 0;
return setInterval(function () {
functions[pos++]();
pos = pos % functions.length;
}, delay);
}
Usage would be like this for you:
var si = cycleWithDelay(peekTile, unpeekTile, 300);
and to stop it:
clearInterval(si);
This will just cycle through the functions calling the next one in the list every delay msec, repeating back at the beginning when the last one is called. This will result in your peekTile, wait, unpeekTile, wait, peekTile, etc.
If you prefer to start/stop at will, perhaps a more generic solution would suit you:
function Cycler(f) {
if (!(this instanceof Cycler)) {
// Force new
return new Cycler(arguments);
}
// Unbox args
if (f instanceof Function) {
this.fns = Array.prototype.slice.call(arguments);
} else if (f && f.length) {
this.fns = Array.prototype.slice.call(f);
} else {
throw new Error('Invalid arguments supplied to Cycler constructor.');
}
this.pos = 0;
}
Cycler.prototype.start = function (interval) {
var that = this;
interval = interval || 1000;
this.intervalId = setInterval(function () {
that.fns[that.pos++]();
that.pos %= that.fns.length;
}, interval);
}
Cycler.prototype.stop = function () {
if (null !== this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
Example usage:
var c = Cycler(peekTile, unpeekTile);
c.start();
// Future
c.stop();
You use setInterval() to call unpeekTile() every 1000 milliseconds and then you call setTimeOut() to run peekTile() after 1000 milliseconds at the end of the unpeekTile() function:
function peekTile() {
var peekAnimation = WinJS.UI.Animation.createPeekAnimation([tile1, tile2]);
// Reposition tiles to their desired post-animation position
tile1.style.top = "-150px";
tile2.style.top = "-150px";
peekAnimation.execute();
}
function unpeekTile() {
/* your code here */
setTimeout(peekTile, 1000);
}
setInterval(unpeekTile, 1000);
Check out the fiddle
var animation = (function () {
var peekInterval, unpeekInterval, delay;
return {
start: function (ip) {
delay = ip;
peekInterval = setTimeout(animation.peekTile, delay);
},
peekTile: function () {
//Your Code goes here
console.log('peek');
unpeekInterval = setTimeout(animation.unpeekTile, delay);
},
unpeekTile: function () {
//Your Code goes here
console.log('unpeek');
peekInterval = setTimeout(animation.peekTile, delay);
},
stop: function () {
clearTimeout(peekInterval);
clearTimeout(unpeekInterval);
}
}
})();
animation.start(1000);
// To stop
setTimeout(animation.stop, 3000);
I can't use this instead of animation.peekTile as setTimeout executes in global scope
Is it possible to chain setTimout functions to ensure they run after one another?
Three separate approaches listed here:
Manually nest setTimeout() callbacks.
Use a chainable timer object.
Wrap setTimeout() in a promise and chain promises.
Manually Nest setTimeout callbacks
Of course. When the first one fires, just set the next one.
setTimeout(function() {
// do something
setTimeout(function() {
// do second thing
}, 1000);
}, 1000);
Chainable Timer Object
You can also make yourself a little utility object that will let you literally chain things which would let you chain calls like this:
delay(fn1, 400).delay(fn2, 500).delay(fn3, 800);
function delay(fn, t) {
// private instance variables
var queue = [], self, timer;
function schedule(fn, t) {
timer = setTimeout(function() {
timer = null;
fn();
if (queue.length) {
var item = queue.shift();
schedule(item.fn, item.t);
}
}, t);
}
self = {
delay: function(fn, t) {
// if already queuing things or running a timer,
// then just add to the queue
if (queue.length || timer) {
queue.push({fn: fn, t: t});
} else {
// no queue or timer yet, so schedule the timer
schedule(fn, t);
}
return self;
},
cancel: function() {
clearTimeout(timer);
queue = [];
return self;
}
};
return self.delay(fn, t);
}
function log(args) {
var str = "";
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
var div = document.createElement("div");
div.innerHTML = str;
var target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
function log1() {
log("Message 1");
}
function log2() {
log("Message 2");
}
function log3() {
log("Message 3");
}
var d = delay(log1, 500)
.delay(log2, 700)
.delay(log3, 600)
Wrap setTimeout in a Promise and Chain Promises
Or, since it's now the age of promises in ES6+, here's similar code using promises where we let the promise infrastructure do the queuing and sequencing for us. You can end up with a usage like this:
Promise.delay(fn1, 500).delay(fn2, 700).delay(fn3, 600);
Here's the code behind that:
// utility function for returning a promise that resolves after a delay
function delay(t) {
return new Promise(function (resolve) {
setTimeout(resolve, t);
});
}
Promise.delay = function (fn, t) {
// fn is an optional argument
if (!t) {
t = fn;
fn = function () {};
}
return delay(t).then(fn);
}
Promise.prototype.delay = function (fn, t) {
// return chained promise
return this.then(function () {
return Promise.delay(fn, t);
});
}
function log(args) {
var str = "";
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
var div = document.createElement("div");
div.innerHTML = str;
var target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
function log1() {
log("Message 1");
}
function log2() {
log("Message 2");
}
function log3() {
log("Message 3");
}
Promise.delay(log1, 500).delay(log2, 700).delay(log3, 600);
The functions you supply to this version can either by synchonrous or asynchronous (returning a promise).
Inspired by #jfriend00 I demonstrated a shorter version:
Promise.resolve()
.then(() => delay(400))
.then(() => log1())
.then(() => delay(500))
.then(() => log2())
.then(() => delay(800))
.then(() => log3());
function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}
If your using Typescript targeting ES6 this is pretty simple with Async Await. This is also very very easy to read and a little upgrade to the promises answer.
//WARNING: this is Typescript source code
//expect to be async
async function timePush(...arr){
function delay(t){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve();
},t)
})
}
//for the length of this array run a delay
//then log, you could always use a callback here
for(let i of arr){
//pass the items delay to delay function
await delay(i.time);
console.log(i.text)
}
}
timePush(
{time:1000,text:'hey'},
{time:5000,text:'you'},
{time:1000,text:'guys'}
);
I have encountered the same issue. My solution was to call self by setTimeout, it works.
let a = [[20,1000],[25,5000],[30,2000],[35,4000]];
function test(){
let b = a.shift();
console.log(b[0]);
if(a.length == 0) return;
setTimeout(test,b[1]);
}
the second element in array a is time to be delayed
Using async / await with #Penny Liu example:
(async() => {
await delay(400)
log1()
await delay(500)
log2()
await delay(800)
log3()
})()
async function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}