7.3: Communicating with the Server Using JavaScript
- Page ID
- 27575
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)This section will describe how to send a transaction using JavaScript from a web browser to the server.
7.3.1 Sending a transaction to the server
There are 4 steps involved in writing a program to send a transaction to the server from JavaScript:
- An HTTP object is created, and the proper values set in that object.
- A callback function (or listener) is set to receive the data back from the server
- The request is sent asynchronously44 to the server.
- The server will process the request, sending back (multiple) responses as it proceeds. Once the request has completed, the server will send back a ready state of 4 and a status code categorizing the result of the transaction.
These 4 steps are shown in the following JavaScript program that sends a request for an array of JSON map objects. This code should be placed in a file named “ReadMaps,html” in the assets directory of your Sails application, and can be accessed using the URL localhost:1337/ReadMaps.html.
Program 114 – XMLHttpRequest example <script> let xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "/mapdata", true); xmlhttp.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function () { console.log("State = " + xmlhttp.readyState + ", and status = " + xmlhttp.status); if(xmlhttp.readyState === 4) { console.log("State = " + xmlhttp.readyState + ", and status = " + xmlhttp.status); } } xmlhttp.send(); </script>
The following will explain each part of the transaction.
- An object variable, in this case named xmlhttp, is created using the constructor function XMLHttpRequest. This object will be used to build the request to be sent to the sever.
- The xmlhttp variable is opened (or initialized) to represent a GET method request on the server, using /mapData as the URL. This is the same request made in Postman to retrieve all of the records in the server. The third parameter is whether this request is asynchronous.
This parameter should always be true. Note that the request has only been built, it has not yet been sent to the server. - A method is defined and attached to the onreadystatechange event to respond to the server after the request is sent. Note that the request is to be sent asynchronously, which means that the request will be running concurrently to the rest of the program. There will be a larger discussion of the implications of this shortly, but for now know that the program will send the request and continue running. The program will not wait for a response from the server. The purpose of this method is to respond to messages coming back from the server.
3.1 The server will send back a number of messages to report the state of the transaction. Generally, there will be at least 3 responses to any request, a state of 2 saying the request was accepted, a state of 3 saying the request is being processed, and a state of 4 saying the request is complete, though you can receive other state information. Your program should always wait until a state 4 is received, meaning the transaction is complete, before continuing 45.
3.2 A status will be returned along with the state. A request that completes normally will have a state of 4 and a status of 200. However, the status can be different depending on the server, and it is a good idea to check these values.
- Once the transaction has been initialized and the callback set, the transaction can be sent to the server using the send method. In this example, the program completes, but the callback function will linger until the request is complete and the xmlhttp object is freed.
The results from running this command are shown in the following figure.
7.3.2 What it means to be asynchronous
Early it was said that this transaction was asynchronous. This has large implications for the program that are not apparent, particularly to programmers not familiar with concurrency. The following example will point out what is meant by concurrency, and why it is an important concept to understand in JavaScript.
The previous program to send a transaction to the server returned the records in the server, however there is no place in the program that displays them. This problem will now be rectified. To fix this problem, the records will be written to the console by using the responseText method of the xmlhttp object. This will contain the records current stored on the server.
The most obvious way to do this would be to place the code to print out the data from the server after the request has been made, as in the following program. This program will not produce the expected output 46.
<script> var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "mapdata", true); http.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function () { } xmlhttp.send() console.log(" text = " + xmlhttp.responseText); </script>
The issue is that the output to the console is occurring while the program is still processing the request, and the xmlhttp property responseText is not yet set when the console.log method is run. To fix this, the traditional answer is to place the output inside the callback so that it is only after the request has completed that the console is written to. This change is represented in the following program:
Program 116 – XMLHttpRequest server request with race condition fixed <script> var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "mapdata", true); xmlhttp.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4) console.log(" text = " + xmlhttp.responseText); } xmlhttp.send(); </script>
Now the program will produce the expected output.
This need to put code inside of a callback can result in callback being inside of a callback inside of a callback. This situation is sometimes call callback hell. It is addressed in ECMA6 by the addition of promise and await operations. This will be covered in a later section.
The last change to this program is to add a check to see if the request worked correctly. If it did, an expected status of 200 will (hopefully) be returned. Otherwise the readyState 4 will have a different status code and message.
7.3.3 Create a record
Now create the file CreateMap.html in the assets directory. This file will contain the following program.
Program 117 – XMLHttpRequest Post example <script> let xmlhttp = new XMLHttpRequest(); let url = "/mapData" xmlhttp.open("POST", url, true); xmlhttp.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function (){ if(xmlhttp.readyState === 4) var allText = xmlhttp.responseText; console.log("status = " + xmlhttp.status + " text = " + allText) } } let newData = { "title": "map2", "resize": false, "recenter": false, "mapType": "Stamen", "screenSize": "1024x768", "latitude": -155, "longitude": 30 } xmlhttp.send(JSON.stringify(newData)) </script>
The major difference between this example and the ReadMaps.html is that now data is being sent with the request. A JavaScript object is created, and that object is serialized to a JSON object and sent along with the request to the server. This will create this new object in your server.
7.3.4 Read, Update, and Delete
The Read, Update, and Delete operations are shown in the following three files, ReadMap, UpdateMap, and DeleteMap. These transaction look similar to the other ReadMaps and Create transactions, but now the URL is changed to include the ID of the item to be acted on.
Program 118 – XMLHttpRequest example to read a record <script> let xmlhttp = new XMLHttpRequest(); let url = "mapData/1" xmlhttp.open("GET", url, true); xmlhttp.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function (){ if(xmlhttp.readyState === 4){ let allText = xmlhttp.responseText; console.log("status = " + xmlhttp.status + " text = " + allText) } } xmlhttp.send() </script>
Program 119 – XMLHttpRequest example to delete a record <script> let xmlhttp = new XMLHttpRequest(); let url = "/mapData/1" xmlhttp.open("Delete", url, true); xmlhttp.setRequestHeader("Content-Type", "application/json"); xmlhttp.onreadystatechange = function (){ if(xmlhttp.readyState === 4){ let allText = xmlhttp.responseText; console.log("status = " + xmlhttp.status + " text = " + allText) } } xmlhttp.send() </script>
44 There is a synchronous version that can be used to send the transactions to the server and using the synchronous version does not require the callbacks and raise the issues involved in processing the asynchronous version. However, using the asynchronous version will tie up the browser so that the user cannot interact with the browser until the request has finished. It is highly recommended that the synchronous version never be used, and it has been deprecated in most of the major libraries.
45 For more information about request state and status, see https://www.w3schools.com/xml/ajax_x...t_response.asp
46 This program represents a race condition, and it is actually possible for the program to have completed the communication with the server before the output condition is executed. If this happens, the program will produce the correct results. The odds of this happening are so vanishingly small that the possibility can be practically discounted.