I run into a problem trying to solve it; but somehow stuck in the middle. The problem is to implement a function $do() which could take any number of functions and execute them in the order they were called.
In the specific example, print a in 700 ms and b in 200, then print c in 100ms. The challenge part is although c should be print first, but it is called after in the $do() after a and b get called.
My implementation as below, almost working, but cannot print c for some reason. Help needed. Thanks a lot.
const a = (cb) => {
setTimeout(() => {
cb();
console.log('a', 700);
}, 700);
};
const b = (cb) => {
setTimeout(() => {
cb();
console.log('b', 200);
}, 200);
};
const c = (cb) => {
setTimeout(() => {
cb();
console.log('c', 100);
}, 100);
};
const stores = [];
let running = false;
function $do() {
const cbs = Array.prototype.slice.call(arguments);
stores.push(cbs);
let i = 0;
while (stores.length > 0 && !running) {
let head = stores.shift();
running = i < head.length;
head.forEach((cb) => {
cb(() => {
i++;
running = i < head.length;
console.log(running);
});
});
}
}
$do(a, b);
$do(c);
This solution uses a closure and Promise.all() to wait for each of the previous pending invocations of $do() to complete before calling each of the functions provided in the current invocation.
This means that c will not be called until both a and b have called back:
const delay = (name, ms) => cb => {
setTimeout(() => {
cb()
console.log(name, ms)
}, ms)
}
const a = delay('a', 700)
const b = delay('b', 200)
const c = delay('c', 100)
const $do = (() => {
let done = Promise.resolve()
return (...fns) => {
done = done.then(
() => Promise.all(fns.map(fn => new Promise(fn)))
)
}
})()
$do(a, b)
$do(c)
Using an IIFE below only to avoid polluting the global namespace unnecessarily
One thing that I'm not sure of is pushing an array onto stores - if there were no "processing" the array would end up
[[a, b],[c]]
and the processing you require would be a, then b, then c - so stores could just as easily be
[a, b, c]
Which is what the code below does, by the way
const a = (cb) => {
setTimeout(() => {
cb();
console.log('a', 700);
}, 700);
};
const b = (cb) => {
setTimeout(() => {
cb();
console.log('b', 200);
}, 200);
};
const c = (cb) => {
setTimeout(() => {
cb();
console.log('c', 100);
}, 100);
};
// NOTE: I only use the IIFE to not polute GLOBAL namespace with stores and running variables
const $do = (() => {
const stores = [];
let running = false;
let process = () => {
const cb = stores.shift();
cb(() => {
running = !!stores.length;
if (running) {
process();
}
});
};
return (...cbs) => {
stores.push(...cbs);
if (!running) {
running = true;
process();
}
};
})();
$do(a, b);
$do(c);
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.
I'm learning throttling and I'm having an issue where my throttle method is not waiting the limit time to run.
const display = (msg) => {
console.log(msg). // I know this function does not do anything, but I'm trying to understand how I can call a function inside my throttle.
}
const throttle = (func, limit) => {
let flag = true;
return function() {
if(flag) {
func.apply(this, arguments);
flag = false;
setTimeout(() => flag = true, limit);
}
}
}
const throttleDisplay = () => {
return throttle(display("Hi"), 6000);
}
for(let i=1; i<=10; i++) {
setTimeout(throttleDisplay, i*1000);
}
My output is "Hi" 10 times, but I shouldn't have 10 times Hi because I have a 6s wait between one call and another.
throttle takes a callback as a parameter, but you're invoking display immediately.
const throttleDisplay = () => {
return throttle(display("Hi"), 6000);
}
is exactly equivalent to
const throttleDisplay = () => {
const result = display("Hi");
return throttle(result, 6000);
}
See the problem?
You need a function that invokes display with the argument you want instead:
const throttleDisplay = () => {
return throttle(() => display("Hi"), 6000);
}
I am making an infinite scroll and the first thing I did was attach an eventListener to detect when the scroll is near-bottom and if so, fire scrollLoader()
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight) {
scrollLoader();
}
})
I made the scrollLoader function below that , for right now, uses a setTimeout to simulate an asynchronous fetch until I have an endpoint. The function returns a function so that loadingData is not on the global scope.
function checkScroll() {
let loadingData;
return function() {
if (loadingData) {
return
}
loadingData = true;
new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 5000)
})
.then(() => {
loadingData = false;
})
}
}
So I would need to instantiate scrollLoader since it returns a function and replace it with the of the variable:
const detectScroll = scrollLoader()
and edit the eventListener to call this now instead of scrollLoader() directly:
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight) {
detectScroll();
}
})
Where exactly do I put this line in my code though ( const detectScroll = scrollLoader() ) ? Anywhere I put it seems out of place since it's just there to instantiate once.
If this is the only spot this code will ever be used, this is the perfect time to use an Immediate Invoked Function Expression (IIFE):
Note the syntax: (()=>{ ... })();
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY + 100 >= document.body.offsetHeight) {
let loadingData;
(() => {
if (loadingData) {
return
}
loadingData = true;
new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 5000)
})
.then(() => {
loadingData = false;
})
})();
}
})
I am trying to make two api calls parallely. When X is getting called, Y should go paralleley and it will call recursively itself until api X resolves.
two Calls fires immediately clicking a button
function X () {
return new Promise((resolve,reject) => {
Service.requestAPI(RequestMethod.GET, API_URL.validateProductStatus)
.then(response => {
resolve(response)
}
}
}
function Y () {
Service.requestAPI(RequestMethod.GET, API_URL.validateProductStatus)
.then(response => {
if(response){
setTimeout(() => {
this.onStatusChange()
},1000)
}
}
}
async function buttonCLick() {
const XResponse = await X();
Y();
}
The above solution doesnt work as expected. The second call Y is becoming sync call instead of async.
Where am i going wrong?
That's what await does, it waits for a async call to complete before resuming.
A lot of code is lacking from your sample to provide a solution specific to your use case. But in general, to do async processing while updating dynamic status, you'll need either callbacks or a stream.
let sleep = ms => new Promise(r => setTimeout(r, ms));
let doAsyncWork = async onProgress => {
let n = 100;
for (let i = 0; i < n; i++) {
await sleep(Math.random() * 100);
onProgress(i / n);
}
onProgress(1);
};
let updateProgress = progress => {
document.querySelector('div').textContent = progress < 1 ?
`Working, ${progress * 100}%` :
`Complete!`;
};
doAsyncWork(updateProgress);
<div></div>
await will block the execution until X is resolved, one way you can achieve this is by using a global variable that determines whether to call Y or not :
let callY = true;
const x = () => {
return new Promise((resolve, reject) => {
// some api call
setTimeout(() => resolve("X is reolved"), 3000);
});
};
const y = () => {
if (callY) {
console.log("waiting for x");
// some api call
// once it resolves based on the response doing a recursive call
setTimeout(() => y(), 1000); // calling the function after 3 seconds
}
};
async function buttonCLick() {
x().then(res => {
console.log(res);
callY = false;
});
y();
}
buttonCLick();
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'));