I am interested in the "debouncing" function in JavaScript, at JavaScript Debounce Function.
Unfortunately the code is not explained clearly enough for me to understand. How does it work (I left my comments below)? In short, I just really do not understand how this works.
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
The copied code snippet previously had callNow in the wrong spot.
The code in the question was altered slightly from the code in the link. In the link, there is a check for (immediate && !timeout) before creating a new time-out. Having it after causes immediate mode to never fire. I have updated my answer to annotate the working version from the link.
function debounce(func, wait, immediate) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
var timeout;
// Calling debounce returns a new anonymous function
return function() {
// reference the context and args for the setTimeout function
var context = this,
args = arguments;
// Should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
var callNow = immediate && !timeout;
// This is the basic debounce behaviour where you can call this
// function several times, but it will only execute once
// (before or after imposing a delay).
// Each time the returned function is called, the timer starts over.
clearTimeout(timeout);
// Set the new timeout
timeout = setTimeout(function() {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
// Check if the function already ran with the immediate flag
if (!immediate) {
// Call the original function with apply
// apply lets you define the 'this' object as well as the arguments
// (both captured before setTimeout)
func.apply(context, args);
}
}, wait);
// Immediate mode and no wait timer? Execute the function...
if (callNow) func.apply(context, args);
}
}
/////////////////////////////////
// DEMO:
function onMouseMove(e){
console.clear();
console.log(e.x, e.y);
}
// Define the debounced function
var debouncedMouseMove = debounce(onMouseMove, 50);
// Call the debounced function on every mouse move
window.addEventListener('mousemove', debouncedMouseMove);
The important thing to note here is that debounce produces a function that is "closed over" the timeout variable. The timeout variable stays accessible during every call of the produced function even after debounce itself has returned, and can change over different calls.
The general idea for debounce is the following:
Start with no timeout.
If the produced function is called, clear and reset the timeout.
If the timeout is hit, call the original function.
The first point is just var timeout;, it is indeed just undefined. Luckily, clearTimeout is fairly lax about its input: passing an undefined timer identifier causes it to just do nothing, it doesn't throw an error or something.
The second point is done by the produced function. It first stores some information about the call (the this context and the arguments) in variables so it can later use these for the debounced call. It then clears the timeout (if there was one set) and then creates a new one to replace it using setTimeout. Note that this overwrites the value of timeout and this value persists over multiple function calls! This allows the debounce to actually work: if the function is called multiple times, timeout is overwritten multiple times with a new timer. If this were not the case, multiple calls would cause multiple timers to be started which all remain active - the calls would simply be delayed, but not debounced.
The third point is done in the timeout callback. It unsets the timeout variable and does the actual function call using the stored call information.
The immediate flag is supposed to control whether the function should be called before or after the timer. If it is false, the original function is not called until after the timer is hit. If it is true, the original function is first called and will not be called any more until the timer is hit.
However, I do believe that the if (immediate && !timeout) check is wrong: timeout has just been set to the timer identifier returned by setTimeout so !timeout is always false at that point and thus the function can never be called. The current version of underscore.js seems to have a slightly different check, where it evaluates immediate && !timeout before calling setTimeout. (The algorithm is also a bit different, e.g. it doesn't use clearTimeout.) That's why you should always try to use the latest version of your libraries. :-)
Debounced functions do not execute when invoked. They wait for a pause of invocations over a configurable duration before executing; each new invocation restarts the timer.
Throttled functions execute and then wait a configurable duration before being eligible to fire again.
Debounce is great for keypress events; when the user starts typing and then pauses you submit all the key presses as a single event, thus cutting down on the handling invocations.
Throttle is great for real-time endpoints that you only want to allow the user to invoke once per a set period of time.
Check out Underscore.js for their implementations too.
I too didn't fully understand how a debounce function worked when I first encountered one. Although relatively small in size, they actually employ some pretty advanced JavaScript concepts! Having a good grip on scope, closures and the setTimeout method will help.
With that said, below is the basic debounce function explained and demoed in my post referenced above.
The finished product
// Create JD Object
// ----------------
var JD = {};
// Debounce Method
// ---------------
JD.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if ( !immediate ) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait || 200);
if ( callNow ) {
func.apply(context, args);
}
};
};
The explanation
// Create JD Object
// ----------------
/*
It's a good idea to attach helper methods like `debounce` to your own
custom object. That way, you don't pollute the global space by
attaching methods to the `window` object and potentially run in to
conflicts.
*/
var JD = {};
// Debounce Method
// ---------------
/*
Return a function, that, as long as it continues to be invoked, will
not be triggered. The function will be called after it stops being
called for `wait` milliseconds. If `immediate` is passed, trigger the
function on the leading edge, instead of the trailing.
*/
JD.debounce = function(func, wait, immediate) {
/*
Declare a variable named `timeout` variable that we will later use
to store the *timeout ID returned by the `setTimeout` function.
*When setTimeout is called, it retuns a numeric ID. This unique ID
can be used in conjunction with JavaScript's `clearTimeout` method
to prevent the code passed in the first argument of the `setTimout`
function from being called. Note, this prevention will only occur
if `clearTimeout` is called before the specified number of
milliseconds passed in the second argument of setTimeout have been
met.
*/
var timeout;
/*
Return an anomymous function that has access to the `func`
argument of our `debounce` method through the process of closure.
*/
return function() {
/*
1) Assign `this` to a variable named `context` so that the
`func` argument passed to our `debounce` method can be
called in the proper context.
2) Assign all *arugments passed in the `func` argument of our
`debounce` method to a variable named `args`.
*JavaScript natively makes all arguments passed to a function
accessible inside of the function in an array-like variable
named `arguments`. Assinging `arguments` to `args` combines
all arguments passed in the `func` argument of our `debounce`
method in a single variable.
*/
var context = this, /* 1 */
args = arguments; /* 2 */
/*
Assign an anonymous function to a variable named `later`.
This function will be passed in the first argument of the
`setTimeout` function below.
*/
var later = function() {
/*
When the `later` function is called, remove the numeric ID
that was assigned to it by the `setTimeout` function.
Note, by the time the `later` function is called, the
`setTimeout` function will have returned a numeric ID to
the `timeout` variable. That numeric ID is removed by
assiging `null` to `timeout`.
*/
timeout = null;
/*
If the boolean value passed in the `immediate` argument
of our `debouce` method is falsy, then invoke the
function passed in the `func` argument of our `debouce`
method using JavaScript's *`apply` method.
*The `apply` method allows you to call a function in an
explicit context. The first argument defines what `this`
should be. The second argument is passed as an array
containing all the arguments that should be passed to
`func` when it is called. Previously, we assigned `this`
to the `context` variable, and we assigned all arguments
passed in `func` to the `args` variable.
*/
if ( !immediate ) {
func.apply(context, args);
}
};
/*
If the value passed in the `immediate` argument of our
`debounce` method is truthy and the value assigned to `timeout`
is falsy, then assign `true` to the `callNow` variable.
Otherwise, assign `false` to the `callNow` variable.
*/
var callNow = immediate && !timeout;
/*
As long as the event that our `debounce` method is bound to is
still firing within the `wait` period, remove the numerical ID
(returned to the `timeout` vaiable by `setTimeout`) from
JavaScript's execution queue. This prevents the function passed
in the `setTimeout` function from being invoked.
Remember, the `debounce` method is intended for use on events
that rapidly fire, ie: a window resize or scroll. The *first*
time the event fires, the `timeout` variable has been declared,
but no value has been assigned to it - it is `undefined`.
Therefore, nothing is removed from JavaScript's execution queue
because nothing has been placed in the queue - there is nothing
to clear.
Below, the `timeout` variable is assigned the numerical ID
returned by the `setTimeout` function. So long as *subsequent*
events are fired before the `wait` is met, `timeout` will be
cleared, resulting in the function passed in the `setTimeout`
function being removed from the execution queue. As soon as the
`wait` is met, the function passed in the `setTimeout` function
will execute.
*/
clearTimeout(timeout);
/*
Assign a `setTimout` function to the `timeout` variable we
previously declared. Pass the function assigned to the `later`
variable to the `setTimeout` function, along with the numerical
value assigned to the `wait` argument in our `debounce` method.
If no value is passed to the `wait` argument in our `debounce`
method, pass a value of 200 milliseconds to the `setTimeout`
function.
*/
timeout = setTimeout(later, wait || 200);
/*
Typically, you want the function passed in the `func` argument
of our `debounce` method to execute once *after* the `wait`
period has been met for the event that our `debounce` method is
bound to (the trailing side). However, if you want the function
to execute once *before* the event has finished (on the leading
side), you can pass `true` in the `immediate` argument of our
`debounce` method.
If `true` is passed in the `immediate` argument of our
`debounce` method, the value assigned to the `callNow` variable
declared above will be `true` only after the *first* time the
event that our `debounce` method is bound to has fired.
After the first time the event is fired, the `timeout` variable
will contain a falsey value. Therfore, the result of the
expression that gets assigned to the `callNow` variable is
`true` and the function passed in the `func` argument of our
`debounce` method is exected in the line of code below.
Every subsequent time the event that our `debounce` method is
bound to fires within the `wait` period, the `timeout` variable
holds the numerical ID returned from the `setTimout` function
assigned to it when the previous event was fired, and the
`debounce` method was executed.
This means that for all subsequent events within the `wait`
period, the `timeout` variable holds a truthy value, and the
result of the expression that gets assigned to the `callNow`
variable is `false`. Therefore, the function passed in the
`func` argument of our `debounce` method will not be executed.
Lastly, when the `wait` period is met and the `later` function
that is passed in the `setTimeout` function executes, the
result is that it just assigns `null` to the `timeout`
variable. The `func` argument passed in our `debounce` method
will not be executed because the `if` condition inside the
`later` function fails.
*/
if ( callNow ) {
func.apply(context, args);
}
};
};
we're all using Promises now
Many implementations I've seen over-complicate the problem or have other hygiene issues. It's 2021 and we've been using Promises for a long time now – and for good reason, too. Promises clean up asynchronous programs and reduce the opportunities for mistakes to happen. In this post we will write our own debounce. This implementation will -
have at most one promise pending at any given time (per debounced task)
stop memory leaks by properly cancelling pending promises
resolve only the latest promise
demonstrate proper behaviour with live code demos
We write debounce with its two parameters, the task to debounce, and the amount of milliseconds to delay, ms. We introduce a single local binding for its local state, t -
function debounce (task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => {
try {
t.cancel()
t = deferred(ms)
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
}
}
We depend on a reusable deferred function, which creates a new promise that resolves in ms milliseconds. It introduces two local bindings, the promise itself, an the ability to cancel it -
function deferred (ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
click counter example
In this first example, we have a button that counts the user's clicks. The event listener is attached using debounce, so the counter is only incremented after a specified duration -
// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
// dom references
const myform = document.forms.myform
const mycounter = myform.mycounter
// event handler
function clickCounter (event) {
mycounter.value = Number(mycounter.value) + 1
}
// debounced listener
myform.myclicker.addEventListener("click", debounce(clickCounter, 1000))
<form id="myform">
<input name="myclicker" type="button" value="click" />
<output name="mycounter">0</output>
</form>
live query example, "autocomplete"
In this second example, we have a form with a text input. Our search query is attached using debounce -
// debounce, deferred
function debounce (task, ms) { let t = { promise: null, cancel: _ => void 0 }; return async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args); } catch (_) { console.log("cleaning up cancelled promise") } } }
function deferred (ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } }
// dom references
const myform = document.forms.myform
const myresult = myform.myresult
// event handler
function search (event) {
myresult.value = `Searching for: ${event.target.value}`
}
// debounced listener
myform.myquery.addEventListener("keypress", debounce(search, 1000))
<form id="myform">
<input name="myquery" placeholder="Enter a query..." />
<output name="myresult"></output>
</form>
multiple debounces, react hook useDebounce
In another Q&A someone asks if its possible to use expose the debounce cancellation mechanism and create a useDebounce React hook. Using deferred above, it's a trivial exercise.
// revised implementation
function debounce(task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return [
// ...,
_ => t.cancel() // ✅ return cancellation mechanism
]
}
// revised usage
const [inc, cancel] = debounce(clickCounter, 1000) // ✅ two controls
myform.mybutton.addEventListener("click", inc)
myform.mycancel.addEventListener("click", cancel)
Implementing a useDebounce React hook is a breeze -
function useDebounce(task, ms) {
const [f, cancel] = debounce(task, ms)
useEffect(_ => cancel) // ✅ auto-cancel when component unmounts
return [f, cancel]
}
Head over to the original Q&A for a complete demo
A simple debounce function:
HTML:
<button id='myid'>Click me</button>
JavaScript:
function debounce(fn, delay) {
let timeoutID;
return function(...args) {
if(timeoutID)
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
fn(...args)
}, delay);
}
}
document.getElementById('myid').addEventListener('click', debounce(() => {
console.log('clicked');
}, 2000));
This is a variation which always fires the debounced function the first time it is called, with more descriptively named variables:
function debounce(fn, wait = 1000) {
let debounced = false;
let resetDebouncedTimeout = null;
return function(...args) {
if (!debounced) {
debounced = true;
fn(...args);
resetDebouncedTimeout = setTimeout(() => {
debounced = false;
}, wait);
} else {
clearTimeout(resetDebouncedTimeout);
resetDebouncedTimeout = setTimeout(() => {
debounced = false;
fn(...args);
}, wait);
}
}
};
You want to do the following: If you try to call a function right after another, the first should be cancelled and the new one should wait for a given timeout and then execute. So in effect you need some way of cancelling the timeout of the first function? But how?
You could call the function, and pass the returning timeout-id and then pass that ID into any new functions. But the solution above is way more elegant.
It effectively makes the timeout variable available in the scope of returned function. So when a 'resize' event is fired, it does not call debounce() again, hence the timeout content is not changed (!) and is still available for the "next function call".
The key thing here is basically that we call the internal function every time we have a resize event. Perhaps it is more clear if we imagine all resize-events is in an array:
var events = ['resize', 'resize', 'resize'];
var timeout = null;
for (var i = 0; i < events.length; i++){
if (immediate && !timeout)
func.apply(this, arguments);
clearTimeout(timeout); // Does not do anything if timeout is null.
timeout = setTimeout(function(){
timeout = null;
if (!immediate)
func.apply(this, arguments);
}
}
You see the timeout is available to the next iteration?
And there is no reason, in my opinion to rename this to content and arguments to args.
A simple debounce method in JavaScript:
Basic HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Debounce Method</title>
</head>
<body>
<button type="button" id="debounce">Debounce Method</button><br />
<span id="message"></span>
</body>
</html>
JavaScript file
var debouncebtn = document.getElementById('debounce');
function debounce(func, delay) {
var debounceTimer;
return function () {
var context = this, args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
func.apply(context, args)
}, delay);
}
}
Driver code
debouncebtn.addEventListener('click', debounce(function() {
document.getElementById('message').innerHTML += '<br/> The button only triggers every 3 seconds how much every you fire an event';
console.log('The button only triggers every 3 seconds how much every you fire an event');
}, 3000))
Runtime example JSFiddle: https://jsfiddle.net/arbaazshaikh919/d7543wqe/10/
Below is a summary of what a debounce function does, explained in a couple of lines with a demo.
A debounce function is a function that will:
at its first execution, schedule the wrapped function to execute after an interval of time with the setTimeout function
(if executed again during this interval):
delete the previous schedule (with the clearTimeOut function)
reschedule a new one (with the setTimeout function)
And the cycle goes on until the interval of time has elapsed and the wrapped function executes.
Adapted from all comments and from this article
function debounce(callBack, interval, leadingExecution) {
// the schedule identifier, if it's not null/undefined, a callBack function was scheduled
let timerId;
return function () {
// Does the previous run has schedule a run
let wasFunctionScheduled = (typeof timerId === 'number');
// Delete the previous run (if timerId is null, it does nothing)
clearTimeout(timerId);
// Capture the environment (this and argument) and wraps the callback function
let funcToDebounceThis = this, funcToDebounceArgs = arguments;
let funcToSchedule = function () {
// Reset/delete the schedule
clearTimeout(timerId);
timerId = null;
// trailing execution happens at the end of the interval
if (!leadingExecution) {
// Call the original function with apply
callBack.apply(funcToDebounceThis, funcToDebounceArgs);
}
}
// Schedule a new execution at each execution
timerId = setTimeout(funcToSchedule, interval);
// Leading execution
if (!wasFunctionScheduled && leadingExecution) callBack.apply(funcToDebounceThis, funcToDebounceArgs);
}
}
function onMouseMove(e) {
console.log(new Date().toLocaleString() + ": Position: x: " + e.x + ", y:" + e.y);
}
let debouncedMouseMove = debounce(onMouseMove, 500);
document.addEventListener('mousemove', debouncedMouseMove);
If you are using react.js
function debounce(func, delay = 600) {
return (args) => {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
func(args);
}, delay);
};
}
const triggerSearch = debounce(handleSearch);
// Event which triggers search.
onSearch={(searchedValue) => {
setSearchedText(searchedValue);// state update
triggerSearch(searchedValue);
}}
Due this state update in the search event which gets triggered on each letter type, this was re-rendering, and all the code with the debounce func was also getting re initiated.
Due to this behaviour of react there was never an active timeout.
function debounce(func, delay = 600) {
return (args) => {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
func(args);
}, delay);
};
}
const triggerSearch = debounce(handleSearch);
To fix that i used a ref named timeout.
const timeout = useRef();
I have a Javascript object that requires 2 calls out to an external server to build its contents and do anything meaningful. The object is built such that instantiating an instance of it will automatically make these 2 calls. The 2 calls share a common callback function that operates on the returned data and then calls another method. The problem is that the next method should not be called until both methods return. Here is the code as I have implemented it currently:
foo.bar.Object = function() {
this.currentCallbacks = 0;
this.expectedCallbacks = 2;
this.function1 = function() {
// do stuff
var me = this;
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
};
this.function2 = function() {
// do stuff
var me = this;
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
};
this.commonCallback = function(resp) {
this.currentCallbacks++;
// do stuff
if (this.currentCallbacks == this.expectedCallbacks) {
// call new method
}
};
this.function1();
this.function2();
}
As you can see, I am forcing the object to continue after both calls have returned using a simple counter to validate they have both returned. This works but seems like a really poor implementation. I have only worked with Javascript for a few weeks now and am wondering if there is a better method for doing the same thing that I have yet to stumble upon.
Thanks for any and all help.
Unless you're willing to serialize the AJAX there is no other way that I can think of to do what you're proposing. That being said, I think what you have is fairly good, but you might want to clean up the structure a bit to not litter the object you're creating with initialization data.
Here is a function that might help you:
function gate(fn, number_of_calls_before_opening) {
return function() {
arguments.callee._call_count = (arguments.callee._call_count || 0) + 1;
if (arguments.callee._call_count >= number_of_calls_before_opening)
fn.apply(null, arguments);
};
}
This function is what's known as a higher-order function - a function that takes functions as arguments. This particular function returns a function that calls the passed function when it has been called number_of_calls_before_opening times. For example:
var f = gate(function(arg) { alert(arg); }, 2);
f('hello');
f('world'); // An alert will popup for this call.
You could make use of this as your callback method:
foo.bar = function() {
var callback = gate(this.method, 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
The second callback, whichever it is will ensure that method is called. But this leads to another problem: the gate function calls the passed function without any context, meaning this will refer to the global object, not the object that you are constructing. There are several ways to get around this: You can either close-over this by aliasing it to me or self. Or you can create another higher order function that does just that.
Here's what the first case would look like:
foo.bar = function() {
var me = this;
var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
In the latter case, the other higher order function would be something like the following:
function bind_context(context, fn) {
return function() {
return fn.apply(context, arguments);
};
}
This function returns a function that calls the passed function in the passed context. An example of it would be as follows:
var obj = {};
var func = function(name) { this.name = name; };
var method = bind_context(obj, func);
method('Your Name!');
alert(obj.name); // Your Name!
To put it in perspective, your code would look as follows:
foo.bar = function() {
var callback = gate(bind_context(this, this.method), 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
In any case, once you've made these refactorings you will have cleared up the object being constructed of all its members that are only needed for initialization.
I can add that Underscore.js has a nice little helper for this:
Creates a version of the function that will only be run after first
being called count times. Useful for grouping asynchronous responses,
where you want to be sure that all the async calls have finished,
before proceeding.
_.after(count, function)
The code for _after (as-of version 1.5.0):
_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
The license info (as-of version 1.5.0)
There is barely another way than to have this counter. Another option would be to use an object {} and add a key for every request and remove it if finished. This way you would know immediately which has returned. But the solution stays the same.
You can change the code a little bit. If it is like in your example that you only need to call another function inside of commonCallback (I called it otherFunction) than you don't need the commonCallback. In order to save the context you did use closures already. Instead of
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
you could do it this way
foo.bar.sendRequest(new RequestObject, function(resp) {
--me.expectedCallbacks || me.otherFunction(resp);
});
That's some good stuff Mr. Kyle.
To put it a bit simpler, I usually use a Start and a Done function.
-The Start function takes a list of functions that will be executed.
-The Done function gets called by the callbacks of your functions that you passed to the start method.
-Additionally, you can pass a function, or list of functions to the done method that will be executed when the last callback completes.
The declarations look like this.
var PendingRequests = 0;
function Start(Requests) {
PendingRequests = Requests.length;
for (var i = 0; i < Requests.length; i++)
Requests[i]();
};
//Called when async responses complete.
function Done(CompletedEvents) {
PendingRequests--;
if (PendingRequests == 0) {
for (var i = 0; i < CompletedEvents.length; i++)
CompletedEvents[i]();
}
}
Here's a simple example using the google maps api.
//Variables
var originAddress = "*Some address/zip code here*"; //Location A
var formattedAddress; //Formatted address of Location B
var distance; //Distance between A and B
var location; //Location B
//This is the start function above. Passing an array of two functions defined below.
Start(new Array(GetPlaceDetails, GetDistances));
//This function makes a request to get detailed information on a place.
//Then callsback with the **GetPlaceDetailsComplete** function
function GetPlaceDetails() {
var request = {
reference: location.reference //Google maps reference id
};
var PlacesService = new google.maps.places.PlacesService(Map);
PlacesService.getDetails(request, GetPlaceDetailsComplete);
}
function GetPlaceDetailsComplete(place, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
formattedAddress = place.formatted_address;
Done(new Array(PrintDetails));
}
}
function GetDistances() {
distService = new google.maps.DistanceMatrixService();
distService.getDistanceMatrix(
{
origins: originAddress,
destinations: [location.geometry.location], //Location contains lat and lng
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, GetDistancesComplete);
}
function GetDistancesComplete(results, status) {
if (status == google.maps.DistanceMatrixStatus.OK) {
distance = results[0].distance.text;
Done(new Array(PrintDetails));
}
}
function PrintDetails() {
alert(*Whatever you feel like printing.*);
}
So in a nutshell, what we're doing here is
-Passing an array of functions to the Start function
-The Start function calls the functions in the array and sets the number of PendingRequests
-In the callbacks for our pending requests, we call the Done function
-The Done function takes an array of functions
-The Done function decrements the PendingRequests counter
-If their are no more pending requests, we call the functions passed to the Done function
That's a simple, but practicle example of sychronizing web calls. I tried to use an example of something that's widely used, so I went with the Google maps api. I hope someone finds this useful.
Another way would be to have a sync point thanks to a timer. It is not beautiful, but it has the advantage of not having to add the call to the next function inside the callback.
Here the function execute_jobs is the entry point. it take a list of data to execute simultaneously. It first sets the number of jobs to wait to the size of the list. Then it set a timer to test for the end condition (the number falling down to 0). And finally it sends a job for each data. Each job decrease the number of awaited jobs by one.
It would look like something like that:
var g_numJobs = 0;
function async_task(data) {
//
// ... execute the task on the data ...
//
// Decrease the number of jobs left to execute.
--g_numJobs;
}
function execute_jobs(list) {
// Set the number of jobs we want to wait for.
g_numJobs = list.length;
// Set the timer (test every 50ms).
var timer = setInterval(function() {
if(g_numJobs == 0) {
clearInterval(timer);
do_next_action();
}
}, 50);
// Send the jobs.
for(var i = 0; i < list.length; ++i) {
async_task(list[i]));
}
}
To improve this code you can do a Job and JobList classes. The Job would execute a callback and decrease the number of pending jobs, while the JobList would aggregate the timer and call the callback to the next action once the jobs are finished.
I shared the same frustration. As I chained more asynchronous calls, it became a callback hell. So, I came up with my own solution. I'm sure there are similar solutions out there, but I wanted to create something very simple and easy to use. Asynq is a script that I wrote to chain asynchronous tasks. So to run f2 after f1, you can do:
asynq.run(f1, f2)
You can chain as many functions as you want. You can also specify parameters or run a series of tasks on elements in an array too. I hope this library can solve your issues or similar issues others are having.