Your last method to add to the Truck.prototype
object is printOrders. This method will get an array of
all of the customer email addresses, iterate through the array, and console.log
the order information.
The code for this method is very similar to other functions and methods you have already written. But it will start out with a bug, which you will find using Chrome’s debugging tools.
Let’s take this step by step. Start by creating the basic version of printOrders in truck.js. In the body, you will retrieve all the coffee orders from the db object. Then you will use the Object.keys method to get an array containing the email addresses for the orders. Finally, you will iterate through the email address array and run a callback function for each element in the array.
... Truck.prototype.deliverOrder = function (customerId) { console.log('Delivering order for ' + customerId); this.db.remove(customerId); }; Truck.prototype.printOrders = function () { var customerIdArray = Object.keys(this.db.getAll()); console.log('Truck #' + this.truckId + ' has pending orders:'); customerIdArray.forEach(function (id) { console.log(this.db.get(id)); }); }; App.Truck = Truck; window.App = App; })(window);
Inside the new printOrders method, you call this.db.getAll to retrieve all the orders as key/value pairs and pass them to Object.keys, which returns an array containing only the keys. You assign this array to the variable customerIdArray.
When you iterate through this array, you pass a callback to forEach. In the body of that callback, you try to get the order associated with an id (the customer email address).
Save and return to the console. Create a new instance of
Truck
and add some coffee orders. Then try your new
printOrders method.
var myTruck = new App.Truck('007', new App.DataStore()); myTruck.createOrder({ emailAddress: '[email protected]', coffee: 'earl grey'}); myTruck.createOrder({ emailAddress: '[email protected]', coffee: 'decaf'}); myTruck.createOrder({ emailAddress: '[email protected]', coffee: 'double mocha'}); myTruck.printOrders();
Instead of a list of the coffee orders, you will see the error
Uncaught TypeError: Cannot read property 'db' of undefined
(Figure 8.15).
This is one of the most common errors that you will see when writing JavaScript. Many developers find it especially frustrating because it can be hard to pinpoint the cause. Knowing how to use the debugger, as you are about to do, is key to locating the problem.
Debugging requires you to reproduce the error as you progressively isolate the buggy code. The Chrome debugger makes this process (almost) enjoyable.
When an error occurs, the console shows you the filename and the
line number of the code that caused the error.
(In Figure 8.15, the reference is to truck.js:30
;
your line number might be different.)
Click that text to open the offending line of code in the debugging tools (Figure 8.16).
You are now viewing the sources panel of the DevTools. Click the red icon in the problem line to see the error information (Figure 8.17).
This error message indicates that the browser thinks you are
trying to read a property named db
, but that the object it belongs
to does not exist.
The next step is to run the code just up to the line that is causing the error and then check the value of that object. In the sources panel, click the line number to the left of the line with the error flag. This sets a breakpoint for the debugger, telling the browser to pause just before it tries to run this line. When you set a breakpoint, the line number on the left turns blue and an entry is added to the breakpoints panel on the right (Figure 8.18).
Press the Escape key to show the console at the bottom of the sources panel (Figure 8.19). This is also known as the drawer. You will need to be able to see the code in the sources panel and interact with the console at the same time.
Run myTruck.printOrders();
again in the console.
The browser will activate the debugger, and your code will pause
at the breakpoint (Figure 8.20).
When the debugger pauses, you have access to all of the variables that are available at that point. Using the console, you can check the values of the variables, looking for signs of trouble.
Try to reproduce the error by evaluating parts of the line of code with the error flag.
Start with the code that is nested furthest inside of any parentheses.
In this case, that is the id variable. When you enter
that on the console, it reports that the value is [email protected]
(Figure 8.21).
Because that did not reproduce the error, try the code
just outside that set of parentheses, this.db.get(id)
.
Evaluate it on the console.
You should see that the error is reported (Figure 8.22).
Now you can further isolate the cause.
Begin evaluating that same piece
of code, but remove parts of it, starting from the right.
You will do this until the error is no longer printed.
Start with this.db.get
.
After that, enter this.db
.
The console continues to report the error (Figure 8.23).
Finally, enter this
.
You are now at the point where the error is not happening (Figure 8.24).
Why does this
have the value undefined
inside of your callback?
Inside of a callback function, this
is not assigned to an object.
You need to explicitly assign one.
This situation is different from your Truck.prototype methods, where this
refers
to the instance of Truck. Even though the callback is inside
of Truck.prototype.printOrder, it has its own this
variable, which is not assigned to a value and is therefore undefined
.
Before fixing your code, you should be familiar with two
other ways you could have located the bug.
If you mouse over the different parts of the code in the sources panel, the
debugger will show you their values. With the mouse
over this
, it shows you that its value is
undefined
(Figure 8.25).
To the right of the code is the scope panel, which contains a list
of variables available. You can see that values for id
and this are shown – and, again, that this
is undefined
(Figure 8.26).
Click the blue button at the top of the right hand panel (Figure 8.27). This unpauses your code, allowing it to resume execution.
Before moving on, remove the breakpoint by clicking the blue line number again. The blue indicator will disappear (Figure 8.28).
Now, it is time to fix that pesky bug!
In JavaScript, the keyword this
inside of a
function is automatically assigned a value when you call that
function. For constructor functions and for
prototype methods, the value of
this
is the instance object. The instance is
called the owner of the function call. Using
the keyword this
gives you access to the
properties of the owner.
As we said earlier, for callback functions this
is not automatically assigned to an object.
You can manually specify what object should be the owner by
using a function’s bind method.
(Remember that JavaScript functions are actually objects
and can have their own properties and methods, such as bind.)
The bind method accepts an object argument and
returns a new version of the function. When you call the new
version, it will use the object argument passed in to bind as the value of
this
inside of the function’s body.
Inside the forEach callback,
this
is undefined
because
the callback has no owner. Fix that by calling
bind and passing it a reference to the
Truck instance.
Add the call to bind in truck.js.
... Truck.prototype.printOrders = function () { var customerIdArray = Object.keys(this.db.getAll()); console.log('Truck #' + this.truckId + ' has pending orders:'); customerIdArray.forEach(function (id) { console.log(this.db.get(id)); }.bind(this)); }; ...
Outside the body of the forEach callback, the keyword
this
refers to the Truck
instance. By adding .bind(this)
immediately
after the anonymous function – but inside the parentheses for the
forEach call – you are passing
forEach a modified version of the anonymous
function. This modified version uses the
Truck instance as its owner.
Save and confirm that the orders are printed correctly. You will need to re-declare myTruck and run createOrder again.
Your output should look like Figure 8.29.
18.191.176.5