By returning Deferreds from RemoteDataStore’s methods, you have a flexible way to use the data sent back from the server.
But you may have noticed that RemoteDataStore’s methods now stray far away from how DataStore’s methods work. If you were to swap a regular DataStore back in, you would see that it no longer works with your application (Figure 14.6).
In Figure 14.6, you can see that instantiating a Truck
with a regular DataStore throws errors and fails to work correctly
with the UI. CoffeeRun expects a Promise
-based DataStore
in order to function correctly.
To remedy this situation,
you are going to change DataStore’s four methods so that they return Promise
s.
jQuery’s Deferred
objects have treated you well. However, because DataStore
is not using the jQuery $.ajax methods you have been using to access Deferred
s,
you will need to use the native Promise
constructor to create and return Promise
s.
In datastore.js, you are going to update the
add method. But first, create a
Promise variable and assign it the value
window.Promise
. While not absolutely
necessary, it is a good idea to continue this pattern of
importing anything from the global scope that you will need
inside of your module.
Inside the add method, create a new variable called promise. Assign it a new instance of Promise. Make sure to return the promise variable at the end of add.
(function (window) { 'use strict'; var App = window.App || {}; var Promise = window.Promise; function DataStore() { this.data = {}; } DataStore.prototype.add = function (key, val) { this.data[key] = val; var promise = new Promise(); return promise; }; ...
The Promise constructor needs a function argument. Pass it an anonymous function that accepts two function arguments, resolve and reject.
... DataStore.prototype.add = function (key, val) { this.data[key] = val; var promise = new Promise(function (resolve, reject) { }); return promise; }; ...
When the Promise
does its work, it will invoke the anonymous function argument
and pass it two values: resolve and
reject. The resolve
function is invoked to change the state of the Promise
object to
fulfilled. The reject function is
invoked to change the state of the Promise
object to
rejected.
Next, move the data storage line
(this.data[key] = val;
) down into the body of
the anonymous function. To make sure that
this.data
correctly refers to the
DataStore’s data instance
variable, bind the anonymous function to
this.
... DataStore.prototype.add = function (key, val) {this.data[key] = val;var promise = new Promise(function (resolve, reject) { this.data[key] = val; }.bind(this)); return promise; }; ...
At the very end of the anonymous function, invoke resolve with no argument.
... DataStore.prototype.add = function (key, val) { var promise = new Promise(function (resolve, reject) { this.data[key] = val; resolve(null); }.bind(this)); return promise; }; ...
Why use null
as the argument? adding a value to the
DataStore does not produce a value, so there
is nothing for it to resolve to.
When you need to explicitly return a non-value you should use null
.
(You could also use resolve(val)
to give the next
function in the chain access to the freshly stored value. For
CoffeeRun, this is not necessary, and therefore
not included as part of the example.)
You could manually update the other three methods using this same pattern of code. But instead of retyping all that code, create a helper function called promiseResolvedWith to create a Promise, resolve it, and return it. Update DataStore.prototype.add to use this helper.
... function DataStore() { this.data = {}; } function promiseResolvedWith(value) { var promise = new Promise(function (resolve, reject) { resolve(value); }); return promise; } DataStore.prototype.add = function (key, val) {var promise = new Promise(function (resolve, reject) {this.data[key] = val;resolve(null);}.bind(this));return promise;return promiseResolvedWith(null); }; ...
promiseResolvedWith is a reusable form
of the Promise
code you wrote in the
add method.
It accepts a parameter called value,
creates a new variable named promise, and
assigns it to a new instance of Promise. It passes an anonymous
function to the Promise constructor that
accepts two
arguments: resolve and
reject. Inside the anonymous function, you invoke
resolve and pass it the value
argument.
In promiseResolvedWith, you do not need
to bind the function argument to
this
, as there are no references to
this that need to be maintained.
Update the other methods to use promiseResolvedWith.
Pass get and getAll the value you were returning in the non-Promise
version. Pass null
to remove.
... DataStore.prototype.get = function (key) {return this.data[key];return promiseResolvedWith(this.data[key]); }; DataStore.prototype.getAll = function () {return this.data;return promiseResolvedWith(this.data); }; DataStore.prototype.remove = function (key) { delete this.data[key]; return promiseResolvedWith(null); }; ...
Finally, update main.js to use a DataStore instead of a RemoteDataStore.
... var remoteDS = new RemoteDataStore(SERVER_URL); var webshim = window.webshim; var myTruck = new Truck('ncc-1701',remoteDS);new DataStore()); window.myTruck = myTruck; ...
After making these changes, save your code and take CoffeeRun for another spin. You should see that it works correctly using DataStore, but makes no Ajax requests (Figure 14.7).
CoffeeRun has taken you on quite a journey!
Along the way, you wrote some pretty serious
JavaScript using IIFEs, callbacks, and Promise
s.
You also got a taste of jQuery, which you used
to manipulate DOM elements and communicate
with a RESTful web service.
It is time to part ways with CoffeeRun and move on. The next app, Chattrbox, is a full-stack chat application. You will not only create the front-end code but also write the server. Do not worry if this is your first server application. You will still be using JavaScript, just not for the browser. Get ready to work with Node.js!
3.15.219.130