Is it possible to chain setTimeout functions in JavaScript? - javascript

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

Related

How can i make so when i click a button it will resolve a promise, and if i click again it will wait untill the previous promise is resolved

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>

Node.js Promise chain resolving too early

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

Retry on Javascript.Promise.reject a limited number of times or until success

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

Javascript Promises: Chain same promise n times, then do something else

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

Simple throttle in JavaScript

I am looking for a simple throttle in JavaScript. I know libraries like lodash and underscore have it, but only for one function it will be overkill to include any of those libraries.
I was also checking if jQuery has a similar function - could not find.
I have found one working throttle, and here is the code:
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
The problem with this is: it fires the function once more after the throttle time is complete. So let's assume I made a throttle that fires every 10 seconds on keypress - if I do keypress 2 times, it will still fire the second keypress when 10 seconds are completed. I do not want this behavior.
I would use the underscore.js or lodash source code to find a well tested version of this function.
Here is the slightly modified version of the underscore code to remove all references to underscore.js itself:
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
Please note that this code can be simplified if you don't need all the options that underscore support.
Please find below a very simple and non-configurable version of this function:
function throttle (callback, limit) {
var waiting = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!waiting) { // If we're not waiting
callback.apply(this, arguments); // Execute users function
waiting = true; // Prevent future invocations
setTimeout(function () { // After a period of time
waiting = false; // And allow future invocations
}, limit);
}
}
}
Edit 1: Removed another reference to underscore, thx to #Zettam 's comment
Edit 2: Added suggestion about lodash and possible code simplification, thx to #lolzery #wowzery 's comment
Edit 3: Due to popular requests, I added a very simple, non-configurable version of the function, adapted from #vsync 's comment
What about this?
function throttle(func, timeFrame) {
var lastTime = 0;
return function () {
var now = Date.now();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
}
Simple.
You may be interested in having a look at the source.
callback: takes the function that should be called
limit: number of times that function should be called within the time limit
time: time span to reset the limit count
functionality and usage: Suppose you have an API that allows user to call it 10 times in 1 minute
function throttling(callback, limit, time) {
/// monitor the count
var calledCount = 0;
/// refresh the `calledCount` varialbe after the `time` has been passed
setInterval(function(){ calledCount = 0 }, time);
/// creating a closure that will be called
return function(){
/// checking the limit (if limit is exceeded then do not call the passed function
if (limit > calledCount) {
/// increase the count
calledCount++;
callback(); /// call the function
}
else console.log('not calling because the limit has exceeded');
};
}
////////////////////////////////////////////////////////////
// how to use
/// creating a function to pass in the throttling function
function cb(){
console.log("called");
}
/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);
Adding to the discussion here (and for more recent visitors), if the reason for not using the almost de facto throttle from lodash is to have a smaller sized package or bundle, then it's possible to include only throttle in your bundle instead of the entire lodash library. For example in ES6, it would be something like:
import throttle from 'lodash/throttle';
Also, there is a throttle only package from lodash called lodash.throttle which can be used with a simple import in ES6 or require in ES5.
I've just needed a throttle/debounce function for window resize event, and being curious, I also wanted to know what these are and how they work.
I've read multiple blog posts and QAs on SO, but they all seem to overcomplicate this, suggest libraries, or just provide descriptions and not simple plain JS implementations.
I won't provide a description since it's plentiful. So here's my implementation:
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
These might need tweaks (e.g., initially the callback isn't called immediately).
See the difference in action (try resizing the window):
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
var cellDefault = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");
window.addEventListener("resize", function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDefault.appendChild(span);
cellDefault.scrollTop = cellDefault.scrollHeight;
});
window.addEventListener("resize", throttle(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellThrottle.appendChild(span);
cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));
window.addEventListener("resize", debounce(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDebounce.appendChild(span);
cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
table {
border-collapse: collapse;
margin: 10px;
}
table td {
border: 1px solid silver;
padding: 5px;
}
table tr:last-child td div {
width: 60px;
height: 200px;
overflow: auto;
}
table tr:last-child td span {
display: block;
}
<table>
<tr>
<td>default</td>
<td>throttle</td>
<td>debounce</td>
</tr>
<tr>
<td id="cellDefault">
<div></div>
</td>
<td id="cellThrottle">
<div></div>
</td>
<td id="cellDebounce">
<div></div>
</td>
</tr>
</table>
JSFiddle
Here's how I implemented throttle function in ES6 in 9LOC, hope it helps
function throttle(func, delay) {
let timeout = null
return function(...args) {
if (!timeout) {
timeout = setTimeout(() => {
func.call(this, ...args)
timeout = null
}, delay)
}
}
}
Click on this link to see how it works.
I've seen a lot of answers here that are way too complex for "a simple throttle in js".
Almost all of the simpler answers just ignore calls made "in throttle" instead of delaying execution to the next interval.
Here's a simple implementation that also handles calls "in throttle":
const throttle = (func, limit) => {
let lastFunc;
let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args);
lastRan = Date.now();
}, limit - (Date.now() - lastRan)); //negative values execute immediately
}
}
This is almost the exact same implementation for a simple debounce. It just adds a calculation for the timeout delay which requires tracking when the function was last ran. See below:
const debounce = (func, limit) => {
let lastFunc;
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args)
}, limit); //no calc here, just use limit
}
}
Simple solution in ES6. Codepen Demo
const handleOnClick = () => {
console.log("hello")
}
const throttle = (func, delay) => {
let timeout = null;
return function (...args) {
if (timeout === null) {
func.apply(this, args);
timeout = setTimeout(() => {
timeout = null;
}, delay)
}
}
}
document.querySelector("#button").addEventListener("click", throttle(handleOnClick, 500))
<button type="button" id="button">Click me</button>
Here's my own version of Vikas post:
throttle: function (callback, limit, time) {
var calledCount = 0;
var timeout = null;
return function () {
if (limit > calledCount) {
calledCount++;
callback();
}
if (!timeout) {
timeout = setTimeout(function () {
calledCount = 0
timeout = null;
}, time);
}
};
}
I find that using setInterval is not a good idea.
With leading and trailing invocations:
const throttle = (fn, ms) => {
let locked = false
return function () {
if (!locked) {
locked = true
fn.apply(this, arguments)
setTimeout(() => {
fn.apply(this, arguments)
locked = false
}, ms)
}
}
}
Test case:
function log({ gender, address }) {
console.log({
name: this.name,
gender,
address,
})
}
const jack = {
name: 'Jack',
log: throttle(log, 3000),
}
Array.from({ length: 5 }, () => jack.log({ gender: 'Male', address: 'LA' }))
I made a npm package with some throttling functions:
npm install function-throttler
throttleAndQueue
Returns a version of your function that can be called at most every W milliseconds, where W is wait. Calls to your func that happen more often than W get queued up to be called every W ms
throttledUpdate
Returns a version of your function that can be called at most every W milliseconds, where W is wait. for calls that happen more often than W the last call will be the one called (last takes precedence)
throttle
limits your function to be called at most every W milliseconds, where W is wait. Calls over W get dropped
There is a library suited for this purpose, it's Backburner.js from Ember.
https://github.com/BackburnerJS/
You'd use it so.
var backburner = new Backburner(["task"]); //You need a name for your tasks
function saySomething(words) {
backburner.throttle("task", console.log.bind(console, words)
}, 1000);
}
function mainTask() {
"This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}
backburner.run(mainTask)
This throttle function is build on ES6. Callback functions takes arguments (args), and still it works wrapped with throttle function. Be free to customize delay time according to your app needs. 1 time per 100ms is used for development mode, event "oninput" is just an example for frequent case of its use:
const callback = (...args) => {
console.count('callback throttled with arguments:', args);
};
throttle = (callback, limit) => {
let timeoutHandler = 'null'
return (...args) => {
if (timeoutHandler === 'null') {
timeoutHandler = setTimeout(() => {
callback(...args)
timeoutHandler = 'null'
}, limit)
}
}
}
window.addEventListener('oninput', throttle(callback, 100));
P.S. As #Anshul explained: throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds."
In below example, try clicking the button multiple times, but the myFunc function would be executed only once in 3 sec.
The function throttle is passed with the function to be executed and the delay.It returns a closure, which is stored in obj.throttleFunc.
Now since obj.throttleFunc stores a closure, the value of isRunning is maintained inside it.
function throttle(func, delay) {
let isRunning;
return function(...args) {
let context = this; // store the context of the object that owns this function
if(!isRunning) {
isRunning = true;
func.apply(context,args) // execute the function with the context of the object that owns it
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc(param) {
console.log(`Called ${this.name} at ${param}th second`);
}
let obj = {
name: "THROTTLED FUNCTION ",
throttleFunc: throttle(myFunc, 3000)
}
function handleClick() {
obj.throttleFunc(new Date().getSeconds());
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
If we don't want the context or arguments to be passed, then a simpler
version of this would be as following:
function throttle(func, delay) {
let isRunning;
return function() {
if(!isRunning) {
isRunning = true;
func()
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc() {
console.log('Called');
}
let throttleFunc = throttle(myFunc, 3000);
function handleClick() {
throttleFunc();
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
I also want to suggest a simple solution for when there is only 1 function you know you will call (for example: Search)
here is what i did in my project
let throttle;
function search() {
if (throttle) {
clearTimeout(throttle);
}
throttle = setTimeout(() => {
sendSearchReq(str)
}, 500);
}
Search is called on input change event
function throttle(targetFunc, delay){
let lastFunc;
let lastTime;
return function(){
const _this = this;
const args = arguments;
if(!lastTime){
targetFunc.apply(_this, args);
lastTime = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function(){
targetFunc.apply(_this, args);
lastTime = Date.now();
}, delay - (Date.now() - lastTime));
}
}
}
Try it :
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));
CodeSandbox
const { now } = Date;
export default function throttle(func, frameDuration) {
let timeout = null;
let latest;
const epoch = now();
function getDurationToNextFrame() {
const elapsed = now() - epoch;
const durationSinceLastFrame = elapsed % frameDuration;
return frameDuration - durationSinceLastFrame;
}
function throttled(...args) {
latest = () => {
func.apply(this, args);
};
if (!timeout) {
timeout = setTimeout(() => {
latest();
timeout = null;
}, getDurationToNextFrame());
}
}
return throttled;
}
Simple throttle function -
Note- Keep on clicking on the button , You'll see console log at first on click and then only after every 5 seconds until you're keep clicking.
HTML -
<button id='myid'>Click me</button>
Javascript -
const throttle = (fn, delay) => {
let lastTime = 0;
return (...args) => {
const currentTime = new Date().getTime();
if((currentTime - lastTime) < delay) {
return;
};
lastTime = currentTime;
return fn(...args);
}
};
document.getElementById('myid').addEventListener('click', throttle((e) => {
console.log('I am clicked');
}, 5000));
We can also implement using a flag-
var expensive = function(){
console.log("expensive functionnns");
}
window.addEventListener("resize", throttle(expensive, 500))
function throttle(expensiveFun, limit){
let flag = true;
return function(){
let context = this;
let args = arguments;
if(flag){
expensiveFun.apply(context, args);
flag = false;
setTimeout(function(){
flag = true;
}, limit);
}
}
}
Here is a bit modernized and simplified version of #clément-prévost answer
function throttle(func, wait, options = {}) {
let timeout = null;
let previous = 0;
const later = (...args) => {
previous = options.leading === false ? 0 : Date.now();
func(...args);
};
return (...args) => {
const now = Date.now();
if (!previous && options.leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func(...args);
} else if (options.trailing !== false) {
clearTimeout(timeout);
timeout = setTimeout(() => later(...args), remaining);
}
};
}
function myFunc(a) {
console.log(`Log: ${a} ${this.val}`);
}
const myFuncThrottled = throttle(myFunc.bind({val: 42}), 1234, {leading: true, trailing: true})
myFuncThrottled(1)
myFuncThrottled(2)
myFuncThrottled(3)
function throttle(CB,ms=300,Id='Identifier for the callback(CB)'){
Id = Id || ""+CB
var N = throttle.N = throttle.N || {}; // Static variable N to store all callbacks ids and their status
if( N[Id] ) return; // already in the queue to run
N[Id] = 1; // add it the queue
setTimeout(()=>{
N[Id] = 0; // remove it from the queue
CB(); // finally call the function
}, ms);
}
for(var i=0;i<100;i++){
throttle(e=>console.log("Hi1"),1e3,'F1');
}
// will only output : Hi1
// this function guarantee the callback to run at least once
Some great solutions here already, but I was looking for a modern version with trailing (and optionally leading) executions, with the last passed arguments provided to each function call:
const throttle = (fn, wait=500, leading=true) => {
let prev, timeout, lastargs;
return (...args) => {
lastargs = args;
if (timeout) return;
timeout = setTimeout(() => {
timeout = null;
prev = Date.now();
// let's do this ... we'll release the stored args as we pass them through
fn.apply(this, lastargs.splice(0, lastargs.length));
// some fancy timing logic to allow leading / sub-offset waiting periods
}, leading ? prev && Math.max(0, wait - Date.now() + prev) || 0 : wait);
};
}
Usage:
x = throttle((...args) => console.log(...args));
let n = 0;
x(++n, 'boom');
x(++n, 'boom');
x(++n, 'boom');
if there will be more than one function defining them one by one would not be maintainable so i would suggest use a helper class to keep values for each
class slowDown {
constructor(cb,timeGap){
this.last = 0
this.run = function(){
let current = Date.now(),
shouldRun = (current - this.last) >= timeGap
if(shouldRun){
cb(current - this.last)
this.last = current
}
}
}
}
// example use
const press = new slowDown(timeElapsed => {
// define function here which you wanted to slow down
console.log("pressed after " + timeElapsed + " ms")
},750)
window.addEventListener("keydown",()=>{
press.run()
})
Below is the simplest throttle I could think of, in 13 LOC. It creates a timeout each time the function is called and cancels the old one. The original function is called with the proper context and arguments, as expected.
function throttle(fn, delay) {
var timeout = null;
return function throttledFn() {
window.clearTimeout(timeout);
var ctx = this;
var args = Array.prototype.slice.call(arguments);
timeout = window.setTimeout(function callThrottledFn() {
fn.apply(ctx, args);
}, delay);
}
}
// try it out!
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));

Categories

Resources