Asynchronous calls allow client applications to react to changes on the server without impacting the users experience and without the need of the user to specifically interact with that interface to receive those updates.
It allows the system to process the results of a given request as soon as the information is received. It will not lock up the application during this period since the execution of this block of code is delayed.
Callbacks, in general, are functions that get passed into other functions, which then execute a block of code. In HTTP requests, a callback can be used as a way of identifying a function to call once a response is received from the web server. The asynchronous nature of it means that the code is not executed in sequence — the function is called only after the response is completed.
Here is an example of structuring an HTTP post request with a callback:
Here is an example of using the callback:
The callback function is defined to execute some given block of code using the response data that was received from the web server. In this case, the data received is a JSON object containing the types of matches that can be selected in a Tic Tac Toe game and those matches will be displayed to the user by updating the DOM.
The getRequest function communicates with the web server asynchronously and will execute the callback when the response comes back with a status code of 200 (OK).
The function displayMatches is the callback. It will be called when the getRequest comes back with a response.
A downside of using callbacks is the ability and ease to which one can send multiple requests to the web server and the ability to control the order in which they are completed. Nesting callbacks can be quite painful and reading the code for it can get quite messy. So, let’s talk about an alternative.
Here is an example of using promises:
The promise contains two functions, one that is executed when the response is successful, called a resolve, and one that is executed when there is an error, called a reject.
When you look at the code line by line, you can see that asynchronous calls don’t actually complete in the order that the code is written. Request.open on line 13 will open a connection and indicate that the request is in the form of a GET, which means that it is requesting information from the web server (as opposed to updating or creating data).
The request.send line at the bottom of the code snippet on line 25 gets completed next, which actually sends the request. When the response is received back, the request.onload code block on line 14 is then processed.
When the requests’ status is 200 (OK), the resolve function is called and includes the response from the request. Otherwise, the reject function is called and throws an error with some information on why there was a problem with the request.
Here is an example of promise chaining:
The .then notation on line 2 takes what was returned from the prior step and executes a function, which in this case is called displayMessage, a function that updates the DOM with some message content. If there are any errors encountered, the function(error…) code on line 6 gets executed instead.
Notice that the next .then has a function on line 10 with no parameter. That is because it doesn’t need any data to complete its task. It doesn’t need the data returned from the prior step.
The get request on line 11 returns a response object. The next .then on line 16 takes this response object and calls a function that updates the DOM with this information or displays an error if something went wrong.
Here is an example of a test that ensures the resolve is called when the request is completed successfully:
During testing, we don’t actually want to perform an asynchronous call, we just want to ensure that the appropriate code block is called. To do this, we can create a mock that will pretend to send back a status code of 200.
Here is an example of the mock:
In the test, we will use this mock to simulate the request and pretend that the request was completed successfully. We can then assert that the resolve returns the expected response object.
Notice the done referenced on lines 22 and 30 of the test — it is a very simple yet important statement that is needed in the test to indicate that the request was completed.
Here is an example of a test that ensures the reject is called in the appropriate case, which in this example is when an error 400 is received.
We can use the mock to return a status code of 400. This will then trigger execution of the reject function and in the assertion we can ensure that an error is received.
Callbacks allow you to execute a function once a response is received. Promises do the same and allow you to specify an easily-readable order for multiple operations as well as handle error cases.