Asynchronous Programming in
Javascript
1
Goals
• Discuss the notion of asynchronous programming in
Javascript
• The gap between “now” and “later”
• The event loop
• Traditional Approach
• Callbacks
• “Callback Hell”
• New Approach
• Promises
2
Event Based == Potential for
Asynchrony
• The fact that Javascript is event-based means that there is the potential for
asynchronous programming
• Some code is executed now and some code is executed later
• Consider this complete Javascript program (to be executed by Node.js)
• console.log("I like Javascript!");
• This looks like an imperative program and it acts like one
• Run it and it will print out "I like Javascript" as expected
• But…
3
The Event Queue
Event
s
Javascrip
E1 E2 E3 …
t
retrieve
Engine {}
{}
{}
{}
{}
{}
{} {} {}
{} {} {}
execute
{}
Registered Event
E0
{}
{} Handlers
{}
Container Note: Event queue shown outside of container for
illustration purposes only
4
The other implication
• Javascript is single-threaded
• It can only do one thing at a
time!
• Namely: execute the
current event handler
• Boon with respect to
“concurrency”
• If you have two event handlers that share a mutable data
structure, you don’t have to worry about race conditions
• only one event handler runs at a time
• It’s not all roses, however; the two event handlers can arbitrarily
interleave and if you care about ordering with respect to
updating that shared data structure then you still have work to
do 5
Adding Events to the Queue
• One way for Javascript programs to add events to the event
queue is via a call to setTimeout()
•setTimeout() is a function that accepts two parameters
• an event handler (i.e. a function), and
• a delay: the delay before the handler gets added to the
event queue
• This causes a timer to be created
• it executes immediately and waits for the specified delay
• it then adds a timeout event to the queue with the specified
event handler
• The Javascript engine will then process that event as normal
6
What’s the Output?
• Back to the potential for asynchrony
• In Javascript, if the code executing now, adds an event to the
event queue (with an associated handler), or registers an event
handler for an event that could happen, then some code is going
to execute later
• What’s the output of this program?
for (var i = 1; i <= 3; i++) {
setTimeout(
function()
{ console.log(i
);
}, 0);
};
• What part of this 7
program executes
asynchronous code runs “later”
• The output is
• 4
• 4
• 4
• while you might think the code is equivalent to
• setTimeout(function(){ console.log(1); }, 0);
• setTimeout(function(){ console.log(2); }, 0);
• setTimeout(function(){ console.log(3); }, 0);
• It instead is capturing the reference to the variable i, which is then
used when the callback runs which is AFTER the three calls to
setTimeout. 8
setTimeout() creates code that will run
later
global.i =
4
Javascrip
T T2 T3
t retrieve
1
Engine {console.log(i)}
{console.log
(i)}
{console.log(i)}
setTimeout() created three timers;
by the time the for loop finished,
the three timers had expired and
added three “timeout”
Container events (and their
handlers) to the queue 9
setTimeout() creates code that will
run later
global.i = The global.i value is set to 4 by
4 the foruse of the ++
loop's
operator
Javascrip
T T3
t retrieve
2
Engine {console.log
(i)}
execute {console.log
(i)}
T1 {console.log When each event handler is executed,
(i)} it uses its reference to i to print out
value
the current
(4)
Container
10
One more example
var start = new Date;
setTimeout(
function() {
var end = new
Date;
console.log('Tim
e elapsed:', end
- start, ‘ms');
}, 500
);
while (new Date -
start < 1000) {};
• What’s the output?
11
The answer
• You will typically see something like
• Time elapsed: 1000 ms
• This should make sense given what we covered above
• If you expected to see:
•Time elapsed: 500 ms
• then you are most likely applying a “multi-threaded” view to
Javascript
• in other words, you might think that the timer starts counting
down and then invokes the “time out” handler concurrently or
in parallel; it doesn’t.
• The timer does indeed run in parallel (it’s being handled by the
container, not the Javascript engine) but it’s action is to ADD a
“timeout” event to the event queue; that event cannot be
processed until the current event is done; in this example the 12
current event is the entire main program!
Callbacks
• The traditional way of handling asynchronous events in Javascript is
by using a callback
• A callback in JavaScript is a function that is passed as an
argument to another function and is executed after the
completion of some operation, often asynchronously. Callbacks
are a core concept in JavaScript, especially for handling
asynchronous tasks like reading files, making network
requests, or waiting for a user action.
13
Key Features of Callbacks:
• Asynchronous behavior: Callbacks are often used in asynchronous
operations. For example, when fetching data from an API, you use a
callback to handle the data once it's received.
• Higher-order functions: Functions that accept other functions as
arguments (like setTimeout, Array.prototype.map,
Array.prototype.forEach) are called higher-order functions.
• Execution order: The callback is executed after the outer function
completes, often once an asynchronous operation finishes.
Example of a Callback:
• Here’s a simple example using a callback to handle the result of an asynchronous
operation
function greet(name, callback) {
console.log('Hello ' + name);
callback(); // Calling the callback function after the greeting.
}
function sayGoodbye() {
console.log('Goodbye!');
}
// Pass the sayGoodbye function as a callback
greet('Alice', sayGoodbye);
Example of Using Callbacks with
Asynchronous Code:
• In asynchronous code, callbacks are often used to handle the result or
any error that occurs during the operation. Here's an example with
setTimeout, which simulates an asynchronous operation:
function fetchData(callback) {
setTimeout(() => {
const data = { name: 'Alice', age: 25 };
callback(null, data); // First argument is for error (null in this case), second is the data.
}, 2000);
}
fetchData(function(error, data) {
if (error) {
console.log('Error:', error);
} else {
console.log('Fetched data:', data);
}
});
Problems with Callbacks: Callback
Hell
• In cases where you have multiple asynchronous tasks, using callbacks
can result in deeply nested functions, known as callback hell or
pyramid of doom. This makes the code difficult to read and maintain.
function task1(callback) {
setTimeout(() => {
console.log('Task 1 completed');
callback();
}, 1000);
}
function task2(callback) {
setTimeout(() => {
console.log('Task 2 completed');
callback();
}, 1000);
}
function task3(callback) {
setTimeout(() => {
console.log('Task 3 completed');
callback();
}, 1000);
}
// Callback hell:
task1(function() {
task2(function() {
task3(function() {
console.log('All tasks completed');
});
});
});
Solutions to Callback Hell:
• To avoid callback hell, JavaScript introduced Promises and later
async/await to handle asynchronous code in a more readable and
manageable way.
• Promises: Represent the eventual result of an asynchronous
operation, which can be resolved or rejected.
• Async/Await: A syntax for working with promises that makes
asynchronous code look more like synchronous code.