JAVASCRIPT ASYNCHRONOUS PROGRAMMING AND CALLBACKS
JavaScript is synchronous by default, and is single threaded. This
means that code cannot create new threads and run in parallel. Find out what
asynchronous code means and how it looks like
- Asynchronicity
in Programming Languages
- JavaScript
- Callbacks
- Handling
errors in callbacks
- The
problem with callbacks
- Alternatives
to callbacks
ASYNCHRONICITY IN PROGRAMMING LANGUAGES
Computers
are asynchronous by design.
Asynchronous
means that things can happen independently of the main program flow.
In
the current consumer computers, every program runs for a specific time slot,
and then it stops its execution to let another program continue its execution.
This thing runs in a cycle so fast that’s impossible to notice, and we think
our computers run many programs simultaneously, but this is an illusion (except
on multiprocessor machines).
Programs
internally use interrupts, a signal that’s emitted to the processor to gain the
attention of the system.
I
won’t go into the internals of this, but just keep in mind that it’s normal for
programs to be asynchronous, and halt their execution until they need
attention, and the computer can execute other things in the meantime. When a
program is waiting for a response from the network, it cannot halt the
processor until the request finishes.
Normally,
programming languages are synchronous, and some provide a way to manage
asynchronicity, in the language or through libraries. C, Java, C#, PHP, Go,
Ruby, Swift, Python, they are all synchronous by default. Some of them handle
async by using threads, spawning a new process.
JAVASCRIPT
JavaScript
is synchronous by default and is single threaded. This means that code cannot
create new threads and run in parallel.
Lines
of code are executed in series, one after another, for example:
const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()
But JavaScript was born inside the
browser, its main job, in the beginning, was to respond to user actions, like
onClick, onMouseOver, onChange, onSubmit and so on. How could it do this with a
synchronous programming model?
The answer was in its environment. The
browser provides a way to do it by providing a set of APIs that can handle this
kind of functionality.
More recently, Node.js introduced a
non-blocking I/O environment to extend this concept to file access, network
calls and so on.
CALLBACKS
You can’t know when a user is going to
click a button, so what you do is, you define an event handler for the click
event. This event handler accepts a function, which will be called when the
event is triggered:
document.getElementById('button').addEventListener('click',
() => {
//item clicked
})
This is the so-called callback.
A callback is a simple function that’s
passed as a value to another function, and will only be executed when the event
happens. We can do this because JavaScript has first-class functions, which can
be assigned to variables and passed around to other functions (called
higher-order functions)
It’s common to wrap all your client
code in a load event listener on the window object, which runs the callback
function only when the page is ready:
window.addEventListener('load',
() => {
//window loaded
//do what you want
})
Callbacks
are used everywhere, not just in DOM events.
One common
example is by using timers:
setTimeout(()
=> {
// runs after 2 seconds
}, 2000)
XHR requests
also accept a callback, in this example by assigning a function to a property
that will be called when a particular event occurs (in this case, the state of
the request changes):
const xhr =
new XMLHttpRequest()
xhr.onreadystatechange
= () => {
if (xhr.readyState === 4) {
xhr.status === 200 ?
console.log(xhr.responseText) : console.error('error')
}
}
xhr.open('GET',
'https://yoursite.com')
xhr.send()
HANDLING ERRORS IN CALLBACKS
How do you
handle errors with callbacks? One very common strategy is to use what Node.js
adopted: the first parameter in any callback function is the error object:
error-first callbacks
If there is
no error, the object is null. If there is an error, it contains some
description of the error and other information.
fs.readFile('/file.json',
(err, data) => {
if (err !== null) {
//handle error
console.log(err)
return
}
//no errors, process data
console.log(data)
})
THE PROBLEM WITH CALLBACKS
Callbacks
are great for simple cases!
However
every callback adds a level of nesting, and when you have lots of callbacks,
the code starts to be complicated very quickly:
window.addEventListener('load',
() => {
document.getElementById('button').addEventListener('click', () => {
setTimeout(() => {
items.forEach(item => {
//your code here
})
}, 2000)
})
})
This is just
a simple 4-levels code, but I’ve seen much more levels of nesting and it’s not
fun.
How do we
solve this?
ALTERNATIVES
TO CALLBACKS
Starting
with ES6, JavaScript introduced several features that help us with asynchronous
code that do not involve using callbacks:
Promises
(ES6)
Async/Await
(ES8)
No comments:
Post a Comment