Index of Terms
- Asynchronous
- Blocking
- Framework
- HTTP Headers
- Middleware
- MVC (Model View Controller)
- Module
- Non-Blocking
- Opinionated
- Route
- Route Handler
- Synchronous
- Thread
- Unopinionated
Synchronous operations performed one after the other in sequence. For example, you might have used or seen someone use a tranceiver or "walkie-talkie": two or more people all have their own device, and it has only one channel for communication. One person has to press a button to open the communications channel to speak. When they are finished speaking, they usually have to use a keyword to indicate that they're finished, such as "Stop" or "Over"; otherwise, the other users might try to open the channel to speak before the first user has finished.
When the first user has finished speaking, another user can press the button on their own device to open the communications channel. Communication continues in this manner, with only one person being allowed to speak at one time. Two people cannot open the channel at the same time: only one person can use it. So every user has to wait until the previous user has finished speaking before they can speak themselves.
Synchronous applications work the same way: they can only perform one task at a time. Many programming languages work this way by default. For example:
// create a <div> element
let div = document.createElement("div");
// set the div's data-menu attribute to the value of index
div.setAttribute("data-menu", index);
// find the <main> element in the document
let main = document.querySelector("main");
// add the new div to the main
main.append(div);
In this example, each statement executes in sequence, one at a time. The createElement() function has to finish before the next function setAttribute() can start working: Each statement has to execute completely before the next statement can execute.
Asynchronous operations operations can be performed simultaneously and independently, and don't have to be performed in sequence. This is achieved by using mutliple threads. For example, if a group of people wanted to communicate, they could use emails: If one person sent an email to everyone else, each recipient could read and respond to the email whenever they wanted. No recipient has to wait for another to send a response, and several recipients can respond at the same time.
In JavaScript, asynchronous operations often make use of callback functions and promises. For example:
first();
greeting(); // we call this, but it won't execute until 3 seconds later
last();
function greeting() {
// setTimeout() tells the program to execute
// the specified callback function after some time has passed
setTimeout(function() {
console.log("Hello, World!");
}, 3000); // Wait for 3 seconds before executing the function
}
function first() {
console.log("I'm first.");
}
function last() {
console.log("I'm last.")
}
If you try this program, you will see the following output in the console:
"I'm first." "I'm last." "Hello, World!"
In this example, there are three functions being called:
first()
, greeting()
, and last()
.
The greeting()
function uses setTimeout()
to set a timer:
setTimeout()
accepts a callback function and then a value
in milliseconds. It will execute the callback after the number
of milliseconds have passed. In this case, it will execute
the callback function after 3 seconds (3000 milliseconds).
The callback function prints "Hello, World!" on the console.
When this script loads, first()
executes and
displays the "I'm first." value in the console. Next,
the greeting()
function executes.
When the greeting()
function executes, it calls
the setTimeout() function. Since there is a timer, no
output appears yet. But because this is asynchronous, the
greeting()
function can finish, it doesn't have
to wait for the callback function to finish executing.
The program can continue with the next function, it doesn't
have to wait for the callback:
last()
executes now, which causes the "I'm last." value
to display.
Eventually, after three seconds have passed, the callback function finishes and "Hello, World!" is output.
In this example, the callback was executing at the same time as the greeting() function was finishing, and while the last() function was executing.
When an operation must be completed before the next one can begin, we say that the first operation is a blocking operation. For example:
// create a <div> element
let div = document.createElement("div");
// set the div's data-menu attribute to the value of index
div.setAttribute("data-menu", index);
// find the <main> element in the document
let main = document.querySelector("main");
// add the new div to the main
main.append(div);
None of the statements in the above example can execute until the previous one is finished. Each statement or operation blocks the next one from executing until it has finished.
In programming this often happens when reading in files: a read() function that reads in a data file must finish reading in all the data before the next operation can begin. This can be an issue with web applications: a user doesn't want to wait for files to load so that the page can finish rendering, and the owner of the page doesn't want the user to see a half-rendered page in the browser!
When an operation does not block other operations, we say that it's a non-blocking operation. In other words, it refers to an operation that doesn't prevent the next operation(s) from executing: an operation can begin without having to wait for the previous one to finish.
In web applications, non-blocking operations are preferable
for working with input/ouput or handling requests.
For example, the JavaScript fetch()
function
allows you to read in a file without blocking the next operation.
The fetch()
function will read in a file's contents
while the script is executing the next set of code. When the
file data has been read, the fetch()
function
notifies the program that the data is ready. The script can
then pause whatever operation it was working on and go and
retrieve the data, and pass it to another function for
processing, then go and continue whatever task it was
working on previously.
// go get the menu.html file
fetch('menu.html')
// when the file has been read, pass it to this callback
.then(function (data) {
return data.text(); // convert file data to plain text
})
// once the data has been converted to text, pass it
// to this callback to build an HTML menu
.then(function (html) {
buildMenu(html);
});
// other code below this that needs to execute
In this example, the fetch()
function is retrieves
the file's contents. This could take some time so the script
will execute other code it might have. When fetch() has finished
reading in the data, we use the then()
function to execute a callback: The then()
is
chained to a fetch() to say "when the fetch is finished, then do this..."
The callback in the first then()
is called
data
- this name can be any valid JavaScript identifier.
The data
parameter contains the file's contents.
This is how the file's contents are passed to the callback.
The callback function takes the file data and converts it into
plain text/html. This could take some time, so the script goes
and executes other code while the conversion takes place.
When the callback has finished, it notifies the script that
the text/html is ready.
The script can now execute the next callback: the second
then()
contains a callback that takes the text/html
data (it is stored in the html
parameter)
and passes it to the buildMenu() function.
Each of the callbacks are non-blocking: they don't block the next operation from taking place. Note that these are also asynchronous: other functions can execute at the same time as the callbacks. Note that asyncronous code is generally also non-blocking.
A framework is code or a component that can be used as a structure or foundation for an application or part of an application. A framework is used by developers to create applications without having to code the application "from scratch", or from the very beginning: they can use the framework as the start and then change it or build upon it.
Before frameworks, we used to develop applications from scratch, from nothing. We would start with the blank screen and start coding. Developers noticed that many apps have similar components. For example, point-of-sale apps all need to record transactions, update inventory as items are sold, keep track of money in/out, keep track of which sales employee performed a transaction, etc.
Another example: many web apps that display data from a database (such as a sports league like NHL, a wiki or fandom site for a game or tv series, reviews of products/media/games/whatever) all need to allow users to search for things:
- grab a search key value from the user
- perform a database query
- return the results
- format them into a response that is sent back to client
Similarly, a web app for a business that sells stuff online would neeed to show a catalog of available products and product data from a database, have an "add to cart" feature, allow a user to check-out, which would include credit card verification, and perhaps some of the same database functionality as previous examples.
Many web apps have user registration, user login/out functionality, user profiles, forgotten password functions, email users (e.g. offers and deals, important news, announcements, and other communications)
As you can see, there are so many "parts" of any of these examples that are common, and many are re-usable in other applications, or future applications that might be needed later.
Instead of re-using components from other apps, devs start developing "frameworks" that contain configurable components that can be re-used in applications.
Examples:
- user registration/login/logout/profile via web pages modules or components that can be added to any page in an application that needs this functionality
- database access: easy, configurable code to read/write to/from a database via web pages
Frameworks are beneficial as they are easy to add to your applications, and they have already been written and tested for you.
Some very popular frameworks for web applications include:
- Express.js: a JavaScript framework used for coding server-side programs with Node.js
- Django: a Python framework used for developing MVC applications using the Python language.
- Angular and React: frameworks that are used for web applications that contain all their features and functionality within a single page.
- Bootstrap: a CSS framework used to create responsive CSS styling and layout for web applications and web pages.
- Vue.js: a JavaScript framework for creating simple, single-page web sites/applications that's great for newer developers
There are many, many more, and the popularity of frameworks changes over time, plus new ones are always appearing.
When you're involved in server-side programming, you'll often have to read or write HTTP headers, or more specifically, request headers and/or response headers. Request headers are part of a Request object and include information about the request. Response headers are part of a response and include information about the response. Http headers are not specifically defined as "request headers" or "respones headers", as many headers can be used for both requests and responses.
For example, there are request headers that start with "Accept-" that state what kind of response the client is willing to accept from the server. The "User-Agent" header contains the full name of the browser and operating system used by the client that made the request. The "Content-Type" header can be used in a request or response to indicate what kind of content is in the request or response body so the recipient knows what to do with it (e.g. is it plain text/html, is it an image, a video, a binary file, form input data?). The "Set-Cookie" header can be included in a response that sets a cookie on the client computer. The "Location" header can be used in a response to tell the client to request a different URL when the response status code is a number in the 300's (e.g. 301 Moved Permanently when a requested resource has been moved to a different URL).
Middleware is used to perform an operation in between other operations: Often in processing a request there's a chain of functions to execute in order to produce the response. In Node.js, middlware is used to perform operations on the request or response after the request is received and before the response is sent.
For example, say that your server receives a request for the /orders/index.html file. The following functions might need to execute:
- a function to log the request
- a function to check the session data and see if the user is logged in
- a function to send an unauthenticated user (not logged in) to login screen
- a function to verify that the logged in user is authorized to view the page they requested
- a function to redirect an unauthorized user (not allowed to view page) to the home page
- function to retrieve the orders and add them to a page that is sent back in a response to the authenticated, authorized user
Each of these functions is a middleware function. The list of functions is called a "chain" or a "stack". When one function is finished, the next function in the chain/stack executes.
As with this particular example, not all those functions will be in the chain for all requests: you can see it depends on the request and the user's info/status.
In Node.js, middleware function can change the request and/or response objects by modifying/adding headers, data, etc. A function can also end the request/response cycle for a specific request. Functions are called in the order they are declared/defined in the code.
There is also third-party middleware, which is middleware written by other developers that you can use in your programs.
MVC is a common design pattern for apps with a user interface (e.g. things to click on, fields where a user can type in data, etc). MVC refers to the following:
- Model
- models or representations of the data used in the application (objects, classes, lists of objects/data)
- View
- the graphical user interfaces (GUI or UI), how the user interacts with the application
- Controller
- the logic, the processing; the code that executes when the user manipulates the view, code that constructs objects (model) from data entered via the view, etc.
MVC allows a developer to modularize code, or separate code in to re-usable chunks. This makes it much easier to create similar functionality in other programs, but also makes it easier to maintain and debug. It's also common to separate different types of code into one or more different files: for example, a View's code might be in HTML files and CSS files, whereas the Controller and Model code might be written in JavaScript files.
As an example, imagine a user login and registration system on an application:
- Model
- the User object that represents a user, with properties such as username, first name, last name, encrypted password, email address, etc.
- usually where there's a database, this models or represents the entities e.g. a record in a User table
- View
- the login screen, where the user types in their credentials, it would also have a Login button and perhaps also a Register button
- the registration screen where the user can enter their name, email, preferred password, etc; this would have a button the user would click to submit their registration information
- the user profile screen where they can edit their saved name, email, change their password or other data; this would have a button that allows the user to save their changes
- Controller
- functions that execute when a Login button is clicked, functions that execute when the Register button is clicked, functions that validate input data, etc.
The MVC design pattern is a common one and used in many applications in many different languages/technologies.
In most programming languages, a module is one or more extra libraries you can import into your application's project code. You might do this in order to use a component or code that someone else has already created, tested, and debugged. For example, if you need to be able to hash or encrypt user-entered passwords, and you want to use the very-popular BCrypt hash algorithm, you can just import the BCrypt module and use the built-in functions it contains to hash passwords.
You can also create your own modules. For example, you might create a module that provides information on the grading system at your school. Your module could include functions such as getGradePoints(grade), getGpa(grades[]), getGradeSystemTable(), etc.
Frameworks are usually opinionated or unopinionated.
An opinionated framework solves a specific problem or set of problems, and it has a specific way to do do it. For example, you have to use the framework a very specific way, have specific directories and files in your project. Opinionated frameworks are great when you want to do something quickly and with as little fuss or trouble as possible.
To understand this a bit better, imagine that you want a desk you can use for studying and taking notes, and also have a place for your laptop when you want to code. You can get such a desk from IKEA: You have to assemble that desk a specific way and do things in a specific order, or else the desk might not be sturdy or might not look right. But if you follow the directions and have all the parts and tools, you will have a fine desk that suits your needs.
Examples of opinionated frameworks include:
- Ruby On Rails (RoR): it has specific ways of doing things and you have to follow its rules. For example, you have to follow a specific directory structure, you have to use certain objects a specific way in a specific order, etc.
- Spring Boot: to create a web app, you are required to have a specific set of directories and certain kinds of files must be organized a certain way. To handle a form submit, you have to use specific objects to pass data from your form to your server-side code and it has to be done a certain way. It's fine for a standard fill-in form but if you want users to be able to choose an input by clicking an image instead of using a standard form input control, it's extremely difficult to work around.
Opinionated frameworks are great if you want something quick that is standard or typical.
The problem with opinionated frameworks is that they are not helpful with variations of those problems. For example, what if you want a desk, but it needs to have a large cushioned shelf on the side for your cats, space for additional monitors, a side table for writing and note-taking, plus a little shelf on the bottom-inside to put your PC tower. You would not find a desk like this at IKEA or other furniture store (trust me, I've been looking for years). You would have to design and build it yourself, or find the individual parts (cat shelf, desk, table, pc shelf with brackets) and assemble them yourself. But in the end, you have a cool, unique desk that is exactly what you needed and wanted.
Unopinionated frameworks have fewer rules and restrictions on how you build your application. You can use whatever objects and functions you want and be creative; you can assemble your app how you want, use the API in whatever way works for you and your application. This means it's much easier (although more work) to build an app that solves unique problems, or solves standard problems in a more customized way.
Express.js (which is based on Node.js) is an unopinionated framework: you can set up your web project directories in any way you like (not that there aren't standards that we prefer to follow to make code easier to maintain), you can use the various modules and functions in whatever way works, and you can even add other modules and frameworks.
There are good things and bad things to both opinionated and unopinionated frameworks:
- Opinionated frameworks are great if you have a standard
problem requiring a typical solution:
- There are not a lot of details to worry about, just create the app and it works.
- Opinionated frameworks don't allow for a lot of customization or flexibility.
- Unopinionated frameworks give you more customization and flexibility, you can do what you want, how you want to do it
- Unopinionated frameworks sometimes have too many choices, too
many ways to do something:
- which is best? which is more efficient? which is more extensible?
- Sometimes the answers conflict with one-another.
In the context of writing server-side code that handles requests, routing is the process of figuring out what function(s) should execute for a specific incoming request. A route is a specific pathway or chain of functions that will execute for a specific request.
For example, your app receives a request to http://www.foobar.com/ and you route that request to load the index.html file.
A more complex example might be when your app receives a GET request to http://www.foobar.com/orders and you route that request to load listOrders.html, but if your app receives a POST request to that same URL, you route that request to a set of functions that process the request body (which likely contains data, as most POST requests do), and then route that request to load addOrder.html.
A route uses a URL Pattern, which is a string containing a pattern of characters/values that are in the URL. For example, if a URL ends with "/orders" you might route that request to a function, but if it ends with "/user" you might route that request to a different function.
A route handler (or router) is code that handles all the routes for a specific application. Like MVC, it's very common to separate all routing code into one or more different files. If you've ever done event handling in a program, think of a route handler similar to an event handler: An event handler executes when a certain event is triggered from a certain component. A route handler is exececuted when a certain URL is requested.
A thread is a single sequence of execution that runs within a program. It's easiest to understand with an everyday example:
Imagine (or maybe you don't have to imagine) you have several assignments due this week in different classes. You are working on the assignment in Math class for about an hour when your brain starts to become unfocused at question #7. You decide to take a break from the Math assignment and work instead of your essay for your Communications class. However, you don't want to forget where you left off in your Math assignment: So you grab a virtual sticky note from an app on your phone or computer and type: "Math, question 7" followed by the URL of the page that has tips for solving question 7.
Then you work on your Communications assignment for a couple of hours before you become sleepy and want to go to bed. You don't want to forget where you left off with your Communications assignment - you were trying to explain the characteristics of professional electronic communications in your own words from various course references - so you make another virtual sticky note with the URLs and page numbers of the resources you were looking at and a summary of the points you were going to make.
After some sleep, you decide to continue with your Math assignment. You check your sticky note, and with the information you left, you're able to easily pick up where you left off. After an hour, while working on question #10, you decide to take a break and go back to your Communications assignment. You edit your Math sticky note with information to help you easily return to your thoughts about question #10, and then look at your sticky note you left for your Communications assignment: this allows you to return to your train of thought you had when you last left your assignment, so you are able to contiue working on the Communications assignment exactly where you left off.
You can only focus on one assignment at a time, and if you wish to work on multiple assignments, you have to stop working on one before you can start (or continue) working on a different one. The sticky notes tell you where to continue working on a specific assignment.
A thread is similar: an application or operating system can only do one thing at a time, but it has to perform several tasks. A thread is a single sequence of tasks or instructions being performed in order. It works a little bit on one task, leaves some "notes" about where it stopped working on that task, and then starts on another task for a while. When it needs to change tasks again, it leaves some "notes" about where it left off so that it can pick that task up again later. It's easily able to resume any task by checking to see where it left off previously.
Many people confuse thread with process: a process is a set of resources (e.g. memory, sockets, etc) set aside to perform one or more tasks. A process may contain one thread, or many threads.