What you will learn
In this chapter, we are going to make some applications. We are going to discover how JavaScript programs can work with functions to control how data is processed and then move on to consider how JavaScript applications can fetch services from the Internet. Finally, we’ll use the Node.js platform to create a JavaScript-powered web server of our own and build an application that can consume services from it. At the end of this chapter, you will know how web applications really work and how to build one of your own.
Create extra Fashion Shop functions
We are going to start off by performing some analysis for the Fashion Shop application that we created in Chapter 10. This will allow us to explore some advanced features of JavaScript functions and discover how easy it is to perform analysis of the contents of arrays of data. We are going start by considering how a JavaScript program can display a list of items.
At the end of Chapter 10, we had created an application to manage the stock in a Fashion Shop. The shop contains a variety of different stock items, each of which is represented by an instance of a particular object. The entire stock is stored as an array of stock items. One of the features of the application lets the user see all the stock in the shop in one long list (Figure 11-1). Now we are going to explore how this works.
The Fashion Shop application produces a stock list, as shown in Figure 11-1. Each stock item is displayed on a line of text. The item details on each line of the list are preceded by an Update button that can be pressed to update the stock details for that item. Let’s look at how the stock list is produced. When the user presses the button to produce the stock list, the following JavaScript function runs:
function doListFashionShop() { createListPage("Stock List", dataStore); }
The function doListFashionShop
calls the function createListPage
. The createListPage
function is provided with two arguments. The first argument is the heading for the list. In this case, the heading is “Stock List.” The second argument is the list of items to be displayed. In this case, the program is listing the entire contents of the data store. The function createListPage
creates the list page and displays it. Let’s have a look at how that works.
The function createList
works through all the list items and calls the function createListElement
for each one. The list element that createListElement
returns is then added to the mainPage
. The mainPage
variable is the HTML element in the document that the browser is displaying. It is how the Fashion Shop application displays data to the user. The final thing we need to look at is the createListElement
function, so let’s see how that works.
The output from this function is an element that is used to display the item in the HTML document for the Fashion Shop application.
<p><button class="itemButton" onclick="doUpdateItem('4')">Update</button> <p class="itemList">Ref:4 Price:10 Stock:14 Description:red plain dress Color:red Pattern:plain<ic:ccc> Size:14</p></p>
This is the HTML produced by createListElement
for one of the stock items. The HTML includes a button that will call the doUpdateItem
function when the button is clicked. The call of the doUpdateItem
function is given an argument, which is the stockRef
of the item being displayed. In the above HTML, the element with stockRef
4 is being displayed. The doUpdateItem
function finds the requested element and displays it for update.
Imogen has found the Fashion Shop application very useful. She especially likes the stock list feature. Now she would like to add some more data analysis features:
A list of items in sorted in order of the number in stock so that she can quickly discover which items are running low
A list of items sorted in descending order of price so that she can identify expensive items that might be worth discounting
A list of items sorted in order of “investment” (price times number of stock items) so that she can identify where she has spent the most money
A list of just the items that have zero stock
The total value of all her stock
You decide to implement this by adding a Data Analysis option to the main menu for the program, as shown in Figure 11-2.
When Imogen clicks the Analysis button, the application will display a list of analysis commands, as shown in Figure 11-3.
This is our first “submenu” in the application. We could have put all the data analysis functions on the same page as all the other functions, but I think it the program is easier to use if some of the less-popular functions are placed on a different menu. We can create the menus just by creating the schemas for each one as we did in Chapter 10 in the “Build a user interface” section. We can then go back and fill in the functions that are required. Imogen can approve our “empty” menus before we write the code that makes them work.
The first three functions requested from Imogen involve sorting the stock data. The items in the Fashion Shop data storage are held in the order that they were added. To meet the first requirement, we want to sort the dataStore
array in order of stock level. Sorting is something that a lot of programs need to do, so the JavaScript array
type provides a sort
method that we can use to sort the array. We just have to tell the sort
method how to decide the order of elements during the sort.
We do this by providing a comparison function that compares two values and returns a result that indicates their order. If the result is negative, it means that the first value is “smaller” than the second. If the result is positive, it means that the first value is “larger” than the second. If the result is zero, it means that the two values are the same. We can generate a stock level comparison return value by subtracting one stock level from another.
function stockOrderCompare(a,b){ return a.stockLevel-b.stockLevel; }
The function stockOrderCompare
takes in two parameters. It gets the stock level values from each item and then returns the result of subtracting one stock level from another. This comparison function can be provided as an argument to the call of the sort
method to get the dataStore
sorted in this order.
dataStore.sort(stockOrderCompare);
We don’t need to know how the sort
method sorts the dataStore
any more than we need to know how JavaScript adds two numbers together. One thing that it is useful to know, however, is that the sorting process will not move any StockItem
values around in memory. The datastore contains an array of references. We can change the order of the references without having to move anything in memory.
function stockOrderCompare(a, b) { return a.stockLevel - b.stockLevel; } function doListStockLevel() { dataStore.sort(stockOrderCompare); createList("Stock Level Order", dataStore); }
This is the code that produces a list of stock item sorted by stock level. The function doListStockLevel
above is called when Imogen selects the option to display the stock sorted in the order of stock level. It sorts the dataStore
array and then uses the createList
function to display the sorted list.
CODE ANALYSIS
Sorting stock items
The Fashion Shop application in the Ch11 Creating ApplicationsCh11-01 Fashion Shop Stock Sort example folder contains the analysis feature that can be used to display a list of stock items that have been sorted by stock level. You might have some questions about how it works.
var names = ["fred", "jim", "ethel"]; names.sort(stockOrderCompare)
> names.sort(stringOrderCompare)
["ethel", "fred", "jim"]
We can control how sort
behaves by changing the function that is used to perform the comparison, but it is rather unwieldly to have to create the function and then use it as an argument to the call of sort
. JavaScript makes it easier to do this by allowing us to declare the comparison function directly in the arguments of the sort
call:
dataStore.sort(function(a,b){ return a.stockLevel-b.stockLevel; });
This sort
does the same job as the original one, but the comparison function is provided as an argument to the call of sort
. This type of function declaration is called an anonymous function because it has no name. The function can’t be called by any other code. It has the double advantage of making the program slightly smaller and also binding the comparison directly to the call of sort
. With the above code, you don’t have to go and find out what the stockOrderCompare
function does. The application in the Ch11 Creating ApplicationsCh11-02 Fashion Shop Simple Function example folder uses this simplified function format to control the sort
behavior.
Arrow functions are a way of making programs even faster to write. Rather than having to type function
to create an anonymous function, you can express the function like this:
dataStore.sort((a,b) => { return a.stockLevel - b.stockLevel});
The parameters to the function are given before the arrow (=>
), and the block of statements that make up the body of the function is given after the arrow. If your function contains only one statement, you can dispense with both the statement block and the return keyword to make a very short arrow function:
dataStore.sort((a,b) => a.stockLevel - b.stockLevel);
The statement above would sort the datastore in ascending order with the lowest stock levels first. What change do you think you would have to make to sort in descending order?
dataStore.sort((a,b) => b.stockLevel - a.stockLevel);
This turns out to be a very small change to the program. We just have to reverse the order of the subtraction, as shown in the statement above.
CODE ANALYSIS
Anonymous functions
Anonymous functions and arrow functions are very powerful JavaScript features, but they are also rather confusing when you first see them.
dataStore.sort(function(a,b){
return a.stockLevel-b.stockLevel;
});
dataStore.sort((a,b) => { return a.stockLevel - b.stockLevel});
Sorting in order of price (which is the second data analysis that Imogen wants) can be achieved by changing the comparison function to compare two stock items by their price properties:
The doListPrice
function is a slightly modified version of the doListStockItem
function. The final sorting command, which sorts by the amount invested in each type of stock, is slightly more complex because the comparison function needs to work out the total value of each stock item. This value will be the price of an item multiplied by its stock level.
function doListInvestment() { dataStore.sort((a,b) => (b.price * b.stockLevel) - (a.price * a.stockLevel)); createList("Investment Order", dataStore); }
The application in the Ch11 Creating ApplicationsCh11-02 Fashion Shop All Sorts folder contains a version of the Fashion Shop application that implements the sorting behavior using arrow functions.
filter
to get items with zero stockThe next thing that Imogen wants is a feature that lists all the stock items that have zero stock. We could create a for-loop that will work through the dataStore
array and pick out all the items with a stockLevel
value of 0
. However, there is a much easier way to do this by using another method provided by the array object. We have seen that all arrays provide a sort
method that can be used to sort their contents on the basis of a particular comparison function. Arrays also provide other useful methods, including one called filter
that works through an array, applying a function that tests each element in the array. If the test evaluates to true
, the array element is added to the result.
I’ve written the test as an arrow function that accepts a stockItem
(which is what is in the array) and returns true
if the stockLevel
property of the item is zero. The filter
function returns a list that is then displayed by the createList
function. As with stockLevel
, the zeroStockList
contains references to stock items, not the items themselves. The Ch11 Creating ApplicationsCh11-03 Fashion Shop Filter example folder holds a version of the Fashion Shop application that will list the stock items with zero stock levels. We can use this technique to filter the list on any criteria that we like by simply changing the behavior of the selection function.
reduce
to calculate the total value of the stockThe next feature that Imogen wants is the ability to calculate the total value of all her stock. We could create a for-of
loop that works through the datastore array, but there is an easier way to do this by using the reduce
method provided by arrays. The reduce
method reduces the content of an array to a single value by performing a reduce
function on each element in the array and adding up all the results. To make things clearer, I’ll start with a named function before converting it into an arrow function.
function addValueToTotal(total,item){ return total+(item.stockLevel*item.price); }
The function addValueToTotal
accepts the current total as the first parameter and a reference to a stock item as the second parameter. It calculates the value of the stock item, adds this to the total, and then returns the result. We can use the reduce
method in the dataStore
array to apply this function to every element in the stock list and work out the total value of the stock:
var total=dataStore.reduce(addValueToTotal, 0);
The first argument to the reduce
method is the function that will be called to update the total. The second parameter is the initial total value, in this case, zero. We can convert the addValueToTotal
function into an arrow function, in which case, the call of reduce
looks like this:
var total = dataStore.reduce( (total, item) => total + (item.stockLevel * item.price), 0);
The application in the Ch11 Creating ApplicationsCh11-04 Fashion Shop Total Value example folder displays the total value of the stock. You can select Total from the Data Analysis menu, and the function displays an alert containing the total stock value. Note that because this program generates new test data each time it is started, you will find that the total value changes each time it is run.
map
to discount all stock itemsYou give Imogen the completed version of the software and she is very happy with it—for a while. Then she comes back with another request. She would like to be able to apply a discount to all the items in stock. She would like to be able to drop all the prices by 5 percent. You could do this with a for
loop that works through the array, but an array provides a function called map
that will apply a given function to every element in the array. To apply a 5 percent discount, you can multiply the price by 0.95.
dataStore.map((item)=>item.price = item.price*0.95);
This single statement will do the job. The map
method accepts a function that has a single parameter. The parameter is the item to be worked on. The statement above will drop the price of every item in stock by 5 percent. You can find this example program in the Ch11 Creating ApplicationsCh11-05 Fashion Shop Discount folder.
MAKE SOMETHING HAPPEN
Create extra Fashion Shop functions
Now that you know how to use the map
, filter
, and reduce
methods, you could add some other functions that Imogen might like. Note that for some of them, you might have to use more than one method to achieve the requested result.
Add a command that removes a 5 percent discount. (Note that this does not mean that you would add 5 percent to all the prices; you will have to add more than that because the price has been discounted.)
Add a command that reduces the price of all items that cost more than $50 by $5. In other words, a dress costing $20 would stay the same price, but a dress costing $60 would be reduced to $55.
Add a command that displays the number of items in stock that are colored red.
In this section, we are going to discover how JavaScript programs can fetch and use data from the Internet. You can use this technique to fetch data from a huge range of sources. We are going to create an application that reads weather information from the OpenWeather service at https://openweathermap.org. It is free to create an account on the OpenWeather site and use it to make a limited number of weather requests each day. I’ve also created some “fixed” weather data on the GitHub site for this book that you can use to test your program if you don’t want to register. The weather information is supplied as a JSON-encoded object that arrives in a string of text. The first thing we must investigate is how to fetch a string of text from a server on the Internet.
Until now, all the actions performed by our JavaScript programs have completed very quickly. However, it can sometimes take a while for information to arrive from the Internet, and sometimes a network request can fail completely. A JavaScript application that uses a network connection will have to deal with slow responses and the possibility that a request might fail. A good way to do this is to make the fetch process asynchronous.
There are two ways that I can go shopping. I can go to a shop, select what I want, pay for it, and take it home. My actions are synchronized with the shop because I must wait for the sales assistant to accept my payment and hand over the goods before the shopping is completed. Alternatively, I can email my shopping list to the shop and ask them to deliver my purchases. Now I can do other things, such as dig in the garden while I wait for my shopping to arrive. My actions are not synchronized with the shop, meaning they are asynchronous. If the shop takes a long time to deliver, it just means that I might have more time for gardening (which is actually not a win for me as it turns out—I’d rather stand in a shop than work in the garden). Up until now, when a JavaScript program has asked for something to be done, the action has been performed synchronously. For example, consider this statement.
descriptionPar = document.createElement("p");
This statement is from the Fashion Shop application. It calls the createElement
method to create a new document element. When this statement is performed, the application waits for the createElement
method to deliver a new document element in the same way that I wait in a shop for my shopping. The createElement
method runs very quickly, so the application is not kept waiting long. However, a function that uses a network connection to fetch data from a distant server could take several seconds to complete. This would pause the application for several seconds. The user would not be able to click any buttons on the screen for that time because the browser only allows a JavaScript application to perform one action at once. If you’ve ever had the experience of a web page “locking up” when you try to use it, you will know how bad this feels.
To get around this problem, JavaScript introduces the idea of promises. A promise is an object that is associated with a task that a program wants to perform. JavaScript provides a method called fetch
, which is used to fetch data from a server. However, the fetch
function does not return the data; instead, it returns a promise to deliver the data at some point in the future.
var fetchPromise = fetch('https://www.begintocodewithjavascript.com/weather.html');
The statement above uses fetch
to fetch the contents of the file at 'https://www.begintocodewithjavascript.com/weather.html'
. This call of fetch
returns a promise object. The variable fetchPromise
refers to this object. The fetch will start to happen asynchronously. You can think of this as the point in the process where I call the shop and ask them to deliver my shopping.
The fetchPromise
object is a link between our application that requested the data and the asynchronous process fetching the data. We set a property on a promise object to identify a function to be called when the promise is kept. A promise object provides a method called then
, which accepts a reference to the method to be called when the promise has been kept.
fetchPromise.then(doGotWeatherResponse);
The statement above tells fetchPromise
to call the function doGotWeatherResponse
when it has finished fetching. So now we need to find out what the doGotWeatherResponse
function does.
The parameter to a function called when a promise is kept is the result delivered by that promise. In the case of fetch
, the parameter is a response
object that describes what happened when it tried to fetch the data. It is possible that a network request can fail, so the first thing that the doGotWeatherResponse
function does is check the ok
property of the response. If this is false, an alert is displayed and the function ends.
If the response is ok
, the doGotWeatherResponse
function starts the process of getting the data. It uses a “promise-powered” process to obtain the JavaScript object from the JSON in the response.
The json
method provided by the response will get the text from the network and convert it from JSON into a JavaScript object. This might take some time. There might be a lot of data to be fetched and decoded, so it makes sense that this process is performed asynchronously, too. The then
method of the JSON promise is used to nominate a method called doGotWeatherObject
to be called when the weather object has been created from the network.
function doGotWeatherObject (weather) { let resultParagraph = document.getElementById('resultParagraph'); resultParagraph.innerText = "Temp: "+ weather.main.temp; }
The doGotWeatherObject
function gets the temperature information from the weather object and displays it in a paragraph on the page.
{"coord":{"lon":-0.34,"lat":53.74}, "weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}], "base":"stations", "main":{"temp":18.14,"feels_like":17.24,"temp_min":18,"temp_max":18.33, "pressure":1019,"humidity":82}, "visibility":10000,"wind":{"speed":3.6,"deg":270}, "clouds":{"all":98},"dt":1599639962, "sys":{"type":1,"id":1515,"country":"GB","sunrise":1599629093,"sunset":1599676358}, "timezone":3600,"id":2645425,"name":"Hull","cod":200}
This is the JSON that describes the weather. You can see that the object contains a main
property that contains a temp
property that is the temperature value. Note that the temperature is delivered as a value in degrees Celsius, not Fahrenheit. The application in the Ch11 Creating ApplicationsCh11-06 Weather Display example folder contains these functions and will display the weather information when the FETCH button (shown in Figure 11-4) is pressed.
The code we have just written will work perfectly well and shows how functions are bound to promises. However, it is also rather long-winded. It turns out that we can use anonymous functions to simplify the code a lot. We can express an anonymous function using the arrow notation. Take a look the “Arrow functions” section above if you want to refresh your understanding of how they work. In this code, we are going to use an arrow function to specify actions to be performed when a promise is fulfilled.
The doGetWeather
function accepts a parameter that gives the network address of the item to be loaded. The function has exactly the same logic as our previous implementation. However, the functions that are to run when the promises are fulfilled are now written as arrow functions that are given as parameters to then
. You can find a weather application that uses this function in the Ch11 Creating ApplicationsCh11-07 Weather Arrow Functions sample folder. You can use this code to read JSON objects from different locations by changing the URL string.
If you just want to read the text from a website rather than the JSON, you can use the text
method, which returns a promise to deliver text rather than a JavaScript object. You can see this in use in the application in the Ch11 Creating ApplicationsCh11-08 Weather Text sample folder.
In JavaScript, as in real life, it can sometimes be impossible to fulfil a promise. Perhaps the network is broken, or the text delivered by the server does not contain a valid JSON string. In these situations, the promise will not fulfill by calling the function nominated by then
. Instead, a promise can call an error-handling function that has been nominated by a call to the catch
method of a promise. The catch
function is used in exactly the same way as the then
function. It accepts a parameter that describes the error that has occurred.
function doGetWeather(url) { fetch(url).then( response => { if(!response.ok){ alert("Fetch failed"); return; } response.json().then( weather => { let resultParagraph = document.getElementById('resultParagraph'); resultParagraph.innerText = "Temp: " + weather.main.temp; }).catch ( error => alert("Bad JSON: " + error)); // json error handler }).catch( error => alert("Bad fetch: " + error)); // fetch error handler }
You can see the two catches in the doGetWeather
function above. They display an alert that contains any error message that was produced when the promise failed.
finally
to enable a button after a fetchThe weather application we have created makes a fetch
request each time the FETCH button is clicked. This can lead to problems. One thing that users quite like to do is repeatedly click a button if they don’t get a response within a second of their first click. In the weather application, this would cause lots of fetch requests. A well-written application would disable the FETCH button once it has been clicked to prevent this behavior. So let’s make our application well-written. A JavaScript program can disable a button by adding a disabled
attribute to the button:
These two statements disable a button with the ID fetchButton
. Once the fetch has completed, the program can enable the button. We could add code to the then
and catch
functions to enable the button, but as you know, I hate writing the same code twice. Instead, we can add a finally
function to a promise that identifies code to run when the promise has completed, regardless of whether it succeeds. This would guarantee that the FETCH button is enabled once the fetch
is complete.
This is the code that I added to remove the disabled
attribute from the FETCH button. You can find it in the sample application in the Ch11 Creating ApplicationsCh11-09 Weather Finally example folder.
CODE ANALYSIS
Investigating promises
The application in the Ch11 Creating ApplicationsCh11-10 Weather Error Handling example folder uses the doGetWeather
function above to display the temperature. It also contains two other buttons that activate two broken fetch
behaviors. The first attempts to load the weather from a nonexistent network address, and the second loads a file that does not contain JSON data. You can use these to experiment with failed promises.
try{
fetch(url).then( response => {
// rest of fetch code here
}
catch{
// handler for errors during fetching
}
Up until now, all the JavaScript applications that we have written have run inside the browser and used the HTML document to interact with the user. This is fine for creating client applications (meaning applications that fetch data and work with it), but it would be useful to be able to create JavaScript applications that could work as servers that provide data for client applications. In this section, we are going to discover how to use a platform called Node.js to create a server application written in JavaScript. Node.js was created by taking the JavaScript component out of a browser and making it into a freestanding application that can host JavaScript code.
MAKE SOMETHING HAPPEN
Install and test Node.js
Before we can use Node.js, we need to install it on our machine. The application is free. There are versions for Windows PC, macOS, and Linux. Open your browser and go to https://nodejs.org/en/download/.
Click the installer for your machine and go through the installation process. Once you have completed the installation, you can start to work with Node.js. The first thing you can do is test that the installation has worked correctly. We can do that by using the terminal in Visual Studio Code. Start Visual Studio Code, open the View menu, and select Terminal. The terminal window will open in the bottom right-hand corner of Visual Studio Code. You use this window to send commands to the operating system of your computer. I’m using a Windows PC, so my commands will be performed by Windows PowerShell. Enter the command node and press Enter.
The Node application starts and displays a command prompt. You should find this remarkably familiar. If you enter a JavaScript command, it will be obeyed in the same way as if you were using the console in the Developer View in your browser.
You can exit from the Node.js command prompt by holding down the CTRL key and pressing D.
The Node.js application provides a way for a computer to run JavaScript applications. If you had entered the command node myprog.js
, the command prompt would have executed the JavaScript program in the myprog.js file. Note that because this application is not running in a browser, it can’t use an HTML document to communicate with the user. In other words, Node.js applications can’t use buttons, paragraphs, and all the other elements because there is nowhere for them to be displayed. The job of Node.js applications is to host processes that provide services. It is perfectly possible that the weather service that we have just used is hosted by a Node.js application running on a server connected to the Internet.
In Chapter 2 in the “HTML and the World Wide Web” section, we discovered that a web browser uses the HTTP protocol to fetch data from web servers. HTTP describes a way that one program (the browser) can ask for resources from another program (the server). A web server is an application that receives HTTP-formatted requests and generates responses.
The Node.js platform is supplied with a module containing a JavaScript program that can respond to HTTP requests and act as a web server. A module is a package of JavaScript code that we want to use. We can include a module in our application by using the require
function to load it. The module is then exposed as an object. The application using the module will then call methods on that object.
var http = require('http');
This statement above creates a variable called http
, which refers to an HTTP instance. Now we can use the HTTP object to create a web server. We can do this by calling the createServer
method:
The statement above uses the createServer
method supplied by the http
object to create a simple web server. The variable server
is set to refer to the new server. The createServer
method accepts a single argument, which is an arrow function that works on two parameters. This function provides the behavior of the server. It will be called each time a client makes a request of the server. The first parameter to the function describes the request that has been received. The second parameter will hold the response that is to be produced. Our server behavior is implemented by an arrow function that accepts the parameters request
and response
. The function above ignores the contents of the request and just prepares a response that is a text message 'Hello from Simple Server'
. Now that we have our server object, we can make it listen for incoming requests.
server.listen(8080);
The listen
method starts the server listening for incoming requests. The listen
method is supplied with an argument specifying the port that the server will listen on. Each computer on the Internet can expose a set of numbered ports that other computers can connect to. When a program connects to a machine, it specifies a port that it wants to use. Port number 80 has been set aside for HTTP access, and browsers will use this port unless told otherwise. Our server will listen on port 8080.
CODE ANALYSIS
Using a server
You can find this sample server application in the examples for this chapter. We will not be using this application from the browser; we will start the server running from within Visual Studio code and then view the page that it generates with a browser. Start by opening the Visual Studio Code application and then open simpleServer.js in the Ch11 Creating ApplicationsCh11-11 HTML Server example folder, as shown below:
To start the server, you need to run the program in simpleServer.js. Make sure that this file is selected in the file browser as shown above. Open the Run menu and select the Start Debugging option. There are several different ways that Visual Studio Code can run your application, so you will be asked to select the environment you want to use:
Click the Node.js option, and the server will start. Now you can open your browser and connect to this server. Because the server is running on your machine, you can use the Internet address localhost. You need to add the port number that you are using to this address, so enter the address localhost:8080 address into the browser and press Enter to open the site.
When you do this, you will see the message from the function that is running inside the server. You might have some questions about what you have just done:
response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.write('Hello from Simple Server');
response.end();
In the “Fetching web pages” section in Chapter 2, we saw that the URL expressing the location of a web page contains a host element (the Internet address of the server) and a path element (the path on the host to the item that the client wants to read). The simple server above returns the same text for any path that you use to read from the host. In other words, the URLs http://localhost:8080/index.html and http://localhost:8080/otherpage.html both return the message Hello from Simple Server
, as would any other path. The first web server used the path component of a URL to identify the file that the server should open and send to the client. We can improve our simple web server to allow it to respond differently to different paths.
The code above implements a server that recognizes two paths: index.html
and otherpage.html
. If the client tries to access any other path, the sever responds with a “page not found” message. Note that the “page not found” message has a status code of 404 to represent this. The program parses the url
property of the request parameter to extract the path that was requested in the URL. This path is then used to control a switch
construction that selects the response to be sent. You can find this example server in the example folder. This server program could be expanded to serve a number of different pages.
CODE ANALYSIS
Using multiple paths
You can find the multiple page server in the Ch11 Creating ApplicationsCh11-12 Multi page server example folder. Start Visual Studio Code and from the examples folder, open multiPageServer.js. Ensure that the multiPageServer.js file is selected in the file browser. Now click Run -> Start Debugging to start the application. Select the node.js environment when prompted by Visual Studio Code. Now open your browser and navigate to: http://localhost:8080/index.html.
The browser will show the text for this URL. Now change the address to http://localhost:8080/otherpage.html and reload the page.
The browser will now display the response for the other page. Finally, you can try a missing page. Change the address to http://localhost:8080/missingpage.html and reload.
If a given path is not recognized, the default
element in the switch
is performed. This displays the “page not found” message. You might have some questions about this code:
var url = require('url');
var parsedUrl = url.parse(request.url);
var path = parsedUrl.pathname;
case "/aboutpage.html": sendResponse(response, "hello from aboutpage.html"); break;
response.setHeader('Content-Type', 'text/html');
query
to add data to a client requestA client can use different path values to select locations on a website, but it would also be useful for the client to be able to send additional data to the server. We can do this by adding a query string to the URL that is sent by the client to the server. You will often see query strings in your browser when you navigate e-commerce sites. A query string starts with a question mark (?
) and is then followed by one or more name-value pairs that are separated by the ampersand (&
) character:
http://localhost:8080/index.htm?no1=2&no2=3
The URL above contains two query values: no1
(set to the value 2
) and no2
(set to the value 3
). These query values are sent to the server in the URL string. We could create a server that accepted these values and returned their sum. The server could use the parse
method from the url
module to extract the query values.
var parsedUrl = url.parse(request.url,true);
We used the url.parse
method in the previous section when we needed to extract the resource path from the incoming URL. The call of parse
is different in that I’ve added the parameter true
to the call, which tells the parse
method to parse the query string in the URL.
var queries = parsedUrl.query;
Once I’ve done this, I can extract the value of the two queries from the parsedUr
, as shown above. Now my addition program can convert these two query strings into numbers and perform the required calculation:
var no1 = Number(queries.no1); var no2 = Number(queries.no2); var result= no1 + no2;
The query values are always sent as strings of text. Our application uses the Number
function to convert the query values into numbers. It then calculates their sum. The listing below is the complete source of an “addition” service, which accepts a query containing two numbers and returns their sum.
var http = require('http'); var url = require('url'); var server = http.createServer((request, response) => { var parsedUrl = url.parse(request.url,true); var queries = parsedUrl.query; var no1 = Number(queries.no1); var no2 = Number(queries.no2); var result= no1 + no2; response.writeHead(200, { 'Content-Type': 'text/plain' }); response.write(String(result)); response.end(); }); server.listen(8080);
The addition service that we created above does not produce a web page as a result. It returns a string that contains the result of adding two query values together. This kind of service is called a web service. It uses HTTP to transfer messages, but the messages do not contain HTML documents. A more complex web service could return an object described by a string of JSON. We’ve already used one web service, the weather service from openweather.org. Now we know how to create our own web services using JavaScript and Node.js to host them. A Node.js application could also use the file system module fs
to open files held on the server and send them as responses.
CODE ANALYSIS
Investigate the addition server
You can find the addition server in the Ch11 Creating ApplicationsCh11-15 Addition Server example folder. Start Visual Studio Code and open additionServer.js in the example folder. Ensure that the file additionServer.js is selected in the file browser. Now use Run -> Start Debugging to start the application. Select the node.js environment when prompted by Visual Studio Code. Now open your browser and navigate to http://localhost:8080/index.html?no1=2&no2=3.
The server program will work out the result of the calculation (2+3
) and return the result. Try changing the values of no1
and no2
and reload the page. The page will update with the new result.
var url = "http://localhost:8080/docalc.html?no1=2&no2=3";
fetch(url).then(response => { response.text().then(result => { let resultParagraph = document.getElementById('resultParagraph'); resultParagraph.innerText = "Result: " + result; }).catch(error => alert("Bad text: " + error)); // text error handler }).catch(error => alert("Bad fetch: " + error)); // fetch error handler
You could use the modules from Node.js to create a complete application server. However, you would have to write a lot of code to decode the requests and build the responses. The good news is that someone has already built a system for creating servers. The system is called Express, and it provides what is called scaffolding, which you can use to construct an application that is powered by HTML pages and web services.
The even better news is that there is also a way of using prebuilt code like Express. It is called node package manager or npm
. The npm
program is provided with the Node.js installation. You use the npm
program to fetch and install prebuilt modules that you can then add into your server programs using the require
function that we have seen before. Each application has a configuration file called a manifest
that identifies the packages that are to be used by the application. This is all especially useful and extremely powerful, but it is all built on the foundations that you have learned in this chapter. You will create arrow functions that give the behaviors of your server, and you will use promises to ensure that none of your functions block the execution of your program. You can find out how to build your first Express application at http://expressjs.com/en/starter/hello-world.html.
The Node.js services that we have created so far have run on our local computer. You would not normally make your computer visible to the Internet so that other people can use your services. Instead, you would host your JavaScript program on an external server. You don’t need to own a server machine. Instead, you can run your JavaScript application as a service in the cloud. You can get started with this for free by creating an Azure account and then using the App Service Extension for Visual Studio Code to configure and deploy the service that you have created. You can find out more at https://azure.microsoft.com/en-us/develop/nodejs/.
In this chapter, you learned how JavaScript code can be embedded directly in a program as anonymous functions and how the arrow notation makes this easier. You also discovered how programs can interact with the network and how the JavaScript promise mechanism allows the applications to contain asynchronous execution. Finally, you have investigated the Node.js platform that allows JavaScript programs to run on a computer and provide services for JavaScript client applications running in a browser.
JavaScript arrays provide a sort
behavior that will sort their content. We used this to sort the Fashion Shop stock list in order of stock level. The order of a sort is determined by a comparison function that is supplied to the sort
behavior. The comparison
function accepts two parameters, which are array elements. It compares the elements in some way and returns a value that is negative, zero, or positive, depending on the order in which they are to be sorted. The comparison
function can be supplied as a named function or as an anonymous function directly as an argument.
An anonymous function can be expressed using an arrow notation in which the parameter list and body of the function are separated by an arrow operator (=>
). If the body of the arrow function is a single statement, it does not need to be enclosed in a block, and if the single statement returns a value, the return keyword can be omitted.
JavaScript arrays provide a filter
behavior. We used filter
to create a list of Fashion Shop stock items with zero stock. The filter
behavior returns another array containing selected elements. The behavior of the filter
is determined by a filter
function that is supplied to the filter
behavior. The filter
function accepts a single parameter and returns true
if the value of that parameter means that it should be included in the filtered results.
JavaScript arrays provide a reduce
behavior, which can convert an array into a single value. We used reduce
to calculate the total value of all the stock in the Fashion Shop. The reduce
behavior is provided with a function that accepts two parameters: the current total and a reference to an element in the array. The function calculates the new total by adding the value for the given element (in our case, the price of the element multiplied by the number in stock) to the total. The reduce
behavior on an array is also provided with a starting value (which is usually 0) for the total.
JavaScript arrays provide a map
behavior that applies a given function to every element in the array. We used this to apply a sale discount to the price of every item for sale in the Fashion Shop. The map
behavior is provided with a function that accepts a single parameter—the element to be updated.
A JavaScript application running inside a web browser can consume the contents of a web service that is located at a particular address on the Internet. The web service can provide the data in the form of a JSON object, which can be decoded by the client application and displayed to the user. We used this create an application that read weather information from a server at openweather.org.
Some JavaScript operations, such as the fetch
function that reads a message from a network, can take a noticeable time to complete. It is important that time-consuming operations such as fetch
do not cause an active application to pause in a noticeable way. If a button-click handler in a web page takes a long time to complete, the web page would appear to have “locked up,” and the user would not be able to interact with it. JavaScript allows long-running operations (such as fetching from a network) to be performed asynchronously.
The JavaScript promise mechanism allows a programmer to invoke operations that will be performed asynchronously. A promise exposes a then
property that can be used to specify JavaScript code that will be performed when the asynchronous behavior “promised” to the calling application has completed. A promise also exposes a catch
property that specifies code to be performed if the asynchronous operation fails.
JavaScript promise operations can be chained together. This allows an action that processes data delivered by a promise to return a promise to produce the processed data. We used this to allow an asynchronous JSON decoder to run directly after a fetch
operation has completed.
A JavaScript promise exposes a finally
property that can be used to specify code that will be run whether the promise is “kept” (the operation completed successfully) or “broken” (the operation could not be completed). We used the finally
property to run code that enabled a button in the HTML document.
The Node.js framework allows a computer to run JavaScript programs without opening HTML files in the browser. The Node application contains a JavaScript runtime environment that will run JavaScript code. Code running inside Node.js cannot interact with the user via an HTML document (because there is no document), but it can provide JavaScript powered services for other applications. Node.js can be downloaded and installed for free on most computing platforms. (It is an open-source application.)
You can run Node (the program that implements the Node.js framework) from the command line. Node can provide a console like that in the browser Developer View, or it can open files containing JavaScript code and run them. You can run Node from the command prompt within Visual Studio Code.
The Node.js framework is supplied with a number of modules. The require
function is used in an application to load a module and make it available for use in a program. One such module is the http
module, which can be used to create web servers and clients.
The http
module in Node.js can be used to create a server. The server will run on a computer and accept HyperText Transport Protocol (HTTP)–formatted messages that will request services from that computer. Clients making requests of the server will use a uniform resource locator (URL) to specify the network address of the server, the port number (if the port is not port 80), and the path to the resource on the server. The local server that we used for the demonstration code has this address: http://localhost:8080/index.html.
The http.createServer
method used to create a server is called with a single argument, which is the service function for that server. The service function takes two parameters: the request and the response. The request parameter contains a description of the request received from the browser, and the response parameter is an object that can be used to build the response.
The http.listen
method is called with a parameter of the port number that the sever is to listen on. The listen
method will call the service function each time a remote client makes a request of the server. The listen
method will never stop running; it will repeatedly respond to requests. Our first server ignored the content of the request parameter and sent a simple text response. This response was sent each time a browser client navigated to the server.
A response contains a status code (which is 200
for success), a header (which is content type information), and the response text (which is the data that the server is sending to the client).
The service
function can parse the request and obtain the path component of the URL that was specified by the client. The service
function can then serve up different responses for different paths.
A URL can also contain query data which is made up of name-value pairs which can be used by the server function to determine the response that is to be provided. We created an addition server that used a query data string containing values for two numbers to be added together. Query data is one way that a client can communicate with a server.
A JavaScript program running inside a browser can use the fetch
function to interact via query data with services provided by a server.
A single Node.js application can both serve up HTML pages for a browser and respond to queries. This is the basis by which many web applications work.
The Node package manager provides a way for prewritten JavaScript frameworks to be incorporated into applications. The Express framework provides the “scaffolding” that can be used to create JavaScript-powered applications that run from a server.
Here are some questions that you might like to ponder about what we have learned in this chapter:
What is an anonymous function?
An anonymous function is one that has no name. That’s probably not a useful answer, though. If you want to call a function by name, you can create it with a name. If you just want some code to give to another part of your application, you can just pass the code itself over. The sort method for an array needs to be given a behavior that tells it how to put items in order. We could pass the sort method the name of a function to call. Alternatively, we can provide the code itself in the form of an anonymous function.
Can I reuse code in an anonymous function?
No. Code in an anonymous function exists in one place in the code and is used from there. If you want to use a behavior in different parts of your program, you can make a named function and then call it by name every time you want to use it in your code.
Are there any performance advantages to using anonymous functions?
No. All that happens is that the JavaScript system places the code that implements the function in particular place in memory and then uses that address to call it. An anonymous function is called in exactly the same way as any other when the program actually runs.
Are there any performance advantages to using arrow functions to denote anonymous functions?
No. The arrow function was created to make it easier for programmers, not to make the program itself run more quickly.
Is there such a thing as an anonymous method?
No. A method will always have a name because it is named element in an object.
What does it mean if a function returns a promise?
A promise is an object that represents a statement of intent. At some point in the future, the code that implements the promise will run and either work (the promise is fulfilled, and the function specified by then
is called) or fail (the promise is broken, and the function specified by catch
is called). The code to implement the promise will run asynchronously, allowing the code that requested the action to continue running.
Can a promise have multiple then
behaviors?
Yes, it can. You use the then
method to tell a promise what to do when the promise is fulfilled. You can call then
multiple times on a given promise. Each call of then
can specify code to run when the promise is fulfilled. When the promise is fulfilled, the then
functions will be called in the order that they were assigned.
Can multiple promises be active at one time?
Yes, they can. As an example, a program might fire off several successive calls to fetch
to obtain multiple resources from the network. Note that because each call is asynchronous, the program has no idea which of the promises will be fulfilled first. It might be that the most-recent fetch
returns its result first. It is the job of the program that requested the promises to synchronize the responses.
What happens if a promise is never kept?
If a promise is never kept, the then
, catch
, and finally
functions will never be called by the promise.
What is the difference between a request and a response in a web server implementation using HTTP?
The request describes what is being asked for by the browser. It is a lump of data that describes the HTTP request. The request contains the URL entered so that the server can determine the path to the resource to be returned. The URL may also contain query items. The response object is used to build the response from the server. The response contains the status code for the response (200
means “worked okay”), header information, and the data to be sent back. The job of the server function is to use the request information to work out what is required and then put that information into a response to be sent back to the client.
How many query items can a URL contain?
A URL can contain many query items and each item can be quite long. If you look at the URL in the browser of an e-commerce site such as Amazon, you will see that the address can contain many query items. These are used by the server to maintain a session with the browser connection.
Can a Node.js application display graphics for the user?
Not directly. The Node.js platform will run JavaScript applications, but it does not maintain an HTML document for the application to communicate with the user. However, a web application running on a machine can be sent HTML documents by an application running under Node.js. The final example application, Ch11 Creating ApplicationsCh11-16 Addition Application, does this.
Can an HTML server application create a “session” with a web client?
HTTP is described as stateless. Each request made by a web client is “atomic.” Once it has completed, the server forgets about it. If you want to create a “session” with a server, the application must provide data to the server program to identify the session it is part of. One way to do this is for code in the browser to use query string values to identify the session.
184.72.135.210