forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
08_error.txt
857 lines (702 loc) · 30.6 KB
/
08_error.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
:chap_num: 8
:prev_link: 07_elife
:next_link: 09_regexp
:load_files: ["code/chapter/08_error.js"]
= Bugs and Error Handling =
[chapterquote="true"]
[quote, Brian Kernighan and P.J. Plauger, The Elements of Programming Style]
____
Debugging is
twice as hard as writing the code in the first place. Therefore, if
you write the code as cleverly as possible, you are, by definition,
not smart enough to debug it.
____
ifdef::interactive_target[]
[chapterquote="true"]
[quote, Master Yuan-Ma, The Book of Programming]
____
Yuan-Ma had written a small program that used many global variables
and shoddy shortcuts. Reading it, a student asked, ‘You warned us
against these techniques, yet I find them in your program. How can
this be?’ The master said, ‘There is no need to fetch a water hose
when the house is not on fire.’
____
endif::interactive_target[]
(((Kernighan+++,+++ Brian)))(((Plaugher+++,+++ P.J.)))(((debugging)))(((error handling)))A program is crystallized thought.
Sometimes those thoughts are confused. Other times, mistakes are
introduced when converting thought into code. Either way, the result
is a flawed program.
(((input)))(((output)))Flaws in a program are usually called ((bug))s.
Bugs can be programmer errors or problems in other systems that the
program interacts with. Some bugs are immediately apparent, while
others are subtle and might remain hidden in a system for years.
Often, problems surface only when a program encounters a situation
that the programmer didn't originally consider. Sometimes such
situations are unavoidable. When the user is asked to input their age
and types _orange_, this puts our program in a difficult position. The
situation has to be anticipated and handled somehow.
== Programmer mistakes ==
(((parsing)))(((analysis)))When it comes to programmer mistakes, our
aim is simple. We want to find them and fix them. Such mistakes can
range from simple ((typo))s that cause the computer to complain as
soon as it lays eyes on our program to subtle mistakes in our
understanding of the way the program operates, causing incorrect
outcomes only in specific situations. Bugs of the latter type can
take weeks to diagnose.
(((programming language)))(((type)))(((static typing)))(((dynamic
typing)))(((run-time error)))(((error)))The degree to which languages
help you find such mistakes varies. Unsurprisingly, JavaScript is at
the “hardly helps at all” end of that scale. Some languages want to
know the types of all your variables and expressions before even
running a program and will tell you right away when a type is used in
an inconsistent way. JavaScript considers types only when actually
running the program, and even then, it allows you to do some clearly
nonsensical things without complaint, such as `x = true * "monkey"`.
(((syntax)))There are some things that JavaScript does complain about,
though. Writing a program that is not syntactically valid will
immediately trigger an error. Other things, such as calling something
that's not a function or looking up a ((property)) on an ((undefined))
value, will cause an error to be reported when the program is running
and encounters the nonsensical action.
(((NaN)))(((error)))But often, your nonsense computation will simply
produce a `NaN` (not a number) or undefined value. And the program
happily continues, convinced that it's doing something meaningful. The
mistake will manifest itself only later, after the bogus value has
traveled though several functions. It might not trigger an error at
all but silently cause the program's output to be wrong. Finding the
source of such problems can be difficult.
(((debugging)))The process of finding mistakes—bugs—in programs is
called _debugging_.
== Strict mode ==
indexsee:[use strict,strict mode]
(((strict mode)))(((syntax)))(((function)))JavaScript can be made a
_little_ more strict by enabling _strict mode_. This is done by
putting the string `"use strict"` at the top of a file or a function
body. Here's an example:
// test: error "ReferenceError: counter is not defined"
[source,javascript]
----
function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}
canYouSpotTheProblem();
// → ReferenceError: counter is not defined
----
(((var keyword)))(((variable,global)))Normally, when you forget to put
`var` in front of your variable, as with `counter` in the example,
JavaScript quietly creates a global variable and uses that. In strict
mode, however, an ((error)) is reported instead. This is very helpful.
It should be noted, though, that this doesn't work when the variable
in question already exists as a global variable, but only when
assigning to it would have created it.
(((this)))(((global object)))(((undefined)))(((strict mode)))Another
change in strict mode is that the `this` binding holds the value
`undefined` in functions that are not called as ((method))s. When
making such a call outside of strict mode, `this` refers to the global
scope object. So if you accidentally call a method or constructor
incorrectly in strict mode, JavaScript will produce an error as soon
as it tries to read something from `this`, rather than happily working
with the global object, creating and reading global variables.
For example, consider the following code, which calls a
((constructor)) without the `new` keyword so that its `this` will
_not_ refer to a newly constructed object:
[source,javascript]
----
function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand
----
(((error)))So the bogus call to `Person` succeeded but returned an
undefined value and created the global variable `name`. In strict
mode, the result is different.
// test: error "TypeError: Cannot set property 'name' of undefined"
[source,javascript]
----
"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined
----
We are immediately told that something is wrong. This is helpful.
(((parameter)))(((variable,naming)))(((with statement)))Strict mode
does a few more things. It disallows giving a function multiple
parameters with the same name and removes certain problematic
language features entirely (such as the `with` statement, which is so
misguided it is not further discussed in this book).
(((debugging)))In short, putting a `"use strict"` at the top of your
program rarely hurts and might help you spot a problem.
== Testing ==
(((test suite)))(((run-time error)))If the language is not going to do
much to help us find mistakes, we'll have to find them the hard way:
by running the program and seeing whether it does the right thing.
Doing this by hand, again and again, is a sure way to drive yourself
insane. Fortunately, it is often possible to write a second program
that automates testing your actual program.
(((Vector type)))As an example, we once again use the `Vector` type.
// include_code
[source,javascript]
----
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
----
We will write a program to check that our implementation of `Vector`
works as intended. Then, every time we change the implementation, we
follow up by running the test program so that we can be reasonably
confident that we didn't break anything. When we add extra
functionality (for example, a new method) to the `Vector` type, we also
add tests for the new feature.
[source,javascript]
----
function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);
if (p1.x !== 10) return "fail: x property";
if (p1.y !== 20) return "fail: y property";
if (p2.x !== -10) return "fail: negative x property";
if (p3.x !== 0) return "fail: x from plus";
if (p3.y !== 25) return "fail: y from plus";
return "everything ok";
}
console.log(testVector());
// → everything ok
----
(((test suite)))(((testing framework)))(((domain-specific
language)))Writing tests like this tends to produce rather repetitive,
awkward code. Fortunately, there exist pieces of software that help
you build and run collections of tests (_test suites_) by providing a
language (in the form of functions and methods) suited to expressing
tests and by outputting informative information when a test fails. These
are called _testing frameworks_.
== Debugging ==
(((debugging)))Once you notice that there is something wrong with your
program because it misbehaves or produces errors, the next step is to
figure out _what_ the problem is.
Sometimes it is obvious. The ((error)) message will point at a
specific line of your program, and if you look at the error
description and that line of code, you can often see the problem.
(((run-time error)))But not always. Sometimes the line that triggered
the problem is simply the first place where a bogus value produced
elsewhere gets used in an invalid way. And sometimes there is no error
message at all—just an invalid result. If you have been solving the
((exercises)) in the earlier chapters, you will probably have already
experienced such situations.
(((decimal number)))(((binary number)))The following example program
tries to convert a whole number to a string in any base (decimal,
binary, and so on) by repeatedly picking out the last ((digit)) and then
dividing the number to get rid of this digit. But the insane output
that it currently produces suggests that it has a ((bug)).
[source,javascript]
----
function numberToString(n, base) {
var result = "", sign = "";
if (n < 0) {
sign = "-";
n = -n;
}
do {
result = String(n % base) + result;
n /= base;
} while (n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
----
(((analysis)))Even if you see the problem already, pretend for a
moment that you don't. We know that our program is malfunctioning, and
we want to find out why.
(((trial and error)))This is where you must resist the urge to start
making random changes to the code. Instead, _think_. Analyze what is
happening and come up with a ((theory)) of why it might be happening.
Then, make additional observations to test this theory—or, if you
don't yet have a theory, make additional observations that might help
you come up with one.
(((console.log)))(((output)))(((debugging)))(((logging)))Putting a few
strategic `console.log` calls into the program is a good way to get
additional information about what the program is doing. In this case,
we want `n` to take the values `13`, `1`, and then `0`. Let's write
out its value at the start of the loop.
----
13
1.3
0.13
0.013
…
1.5e-323
----
(((rounding)))_Right_. Dividing 13 by 10 does not produce a whole
number. Instead of `n /= base`, what we actually want is `n =
Math.floor(n / base)` so that the number is properly “shifted” to the
right.
(((JavaScript console)))(((breakpoint)))(((debugger statement)))An
alternative to using `console.log` is to use the _debugger_
capabilities of your browser. Modern browsers come with the ability to
set a _breakpoint_ on a specific line of your code. This will cause
the execution of the program to pause every time the line with the
breakpoint is reached and allow you to inspect the values of
variables at that point. I won't go into details here since debuggers
differ from browser to browser, but look in your browser's developer
tools and search the Web for more information. Another way to set a
breakpoint is to include a `debugger` statement (consisting of simply
that keyword) in your program. If the ((developer tools)) of your
browser are active, the program will pause whenever it reaches that
statement, and you will be able to inspect its state.
== Error propagation ==
(((input)))(((output)))(((run-time
error)))(((error)))(((validation)))Not all problems can be prevented
by the programmer, unfortunately. If your program communicates with
the outside world in any way, there is a chance that the input it gets
will be invalid or that other systems that it tries to talk to are
broken or unreachable.
(((error recovery)))Simple programs, or programs that run only under
your supervision, can afford to just give up when such a problem
occurs. You'll look into the problem and try again. “Real”
applications, on the other hand, are expected to not simply crash.
Sometimes the right thing to do is take the bad input in stride and
continue running. In other cases, it is better to report to the user
what went wrong and then give up. But in either situation, the program
has to actively do something in response to the problem.
(((promptInteger function)))(((validation)))Say you have a function
`promptInteger` that asks the user for a whole number and returns it.
What should it return if the user inputs _orange_?
(((null)))(((undefined)))(((return value)))(((special return
value)))One option is to make it return a special value. Common
choices for such values are `null` and `undefined`.
// test: no
[source,javascript]
----
function promptNumber(question) {
var result = Number(prompt(question, ""));
if (isNaN(result)) return null;
else return result;
}
console.log(promptNumber("How many trees do you see?"));
----
This is a sound strategy. Now any code that calls `promptNumber` must
check whether an actual number was read and, failing that, must
somehow recover—maybe by asking again or by filling in a default
value. Or it could again return a special value to _its_ caller to
indicate that it failed to do what it was asked.
(((error handling)))In many situations, mostly when ((error))s are
common and the caller should be explicitly taking them into account,
returning a special value is a perfectly fine way to indicate an
error. It does, however, have its downsides. First, what if the
function can already return every possible kind of value? For such a
function, it is hard to find a special value that can be distinguished
from a valid result.
(((special return value)))(((readability)))The second issue with
returning special values is that it can lead to some very cluttered
code. If a piece of code calls `promptNumber` 10 times, it has to
check 10 times whether `null` was returned. And if its response to
finding `null` is to simply return `null` itself, the caller will in
turn have to check for it, and so on.
== Exceptions ==
(((error handling)))When a function cannot proceed normally, what we
would _like_ to do is just stop what we are doing and immediately jump
back to a place that knows how to handle the problem. This is what
_((exception handling))_ does.
(((control flow)))(((raising (exception))))(((throw keyword)))(((call
stack)))Exceptions are a mechanism that make it possible for code that
runs into a problem to _raise_ (or _throw_) an exception, which is
simply a value. Raising an exception somewhat resembles a
super-charged return from a function: it jumps out of not just the
current function but also out of its callers, all the way down to the
first call that started the current execution. This is called
_((unwinding the stack))_. You may remember the stack of function
calls that was mentioned in link:03_functions.html#stack[Chapter 3].
An exception zooms down this stack, throwing away all the call
contexts it encounters.
(((error handling)))(((syntax)))(((catch keyword)))If exceptions
always zoomed right down to the bottom of the stack, they would not be
of much use. They would just provide a novel way to blow up your
program. Their power lies in the fact that you can set “obstacles”
along the stack to _catch_ the exception as it is zooming down. Then
you can do something with it, after which the program continues
running at the point where the exception was caught.
Here's an example:
[[look]]
[source,javascript]
----
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}
function look() {
if (promptDirection("Which way?") == "L")
return "a house";
else
return "two angry bears";
}
try {
console.log("You see", look());
} catch (error) {
console.log("Something went wrong: " + error);
}
----
(((exception handling)))(((block)))(((throw keyword)))(((try
keyword)))(((catch keyword)))The `throw` keyword is used to raise an
exception. Catching one is done by wrapping a piece of code in a `try`
block, followed by the keyword `catch`. When the code in the `try`
block causes an exception to be raised, the `catch` block is
evaluated. The variable name (in parentheses) after `catch` will be
bound to the exception value. After the `catch` block finishes—or if
the `try` block finishes without problems—control proceeds beneath the
entire `try/catch` statement.
(((debugging)))(((call stack)))(((Error type)))(((stack
trace)))In this case, we used the `Error` ((constructor)) to create
our exception value. This is a ((standard)) JavaScript constructor
that creates an object with a `message` property. In modern JavaScript
environments, instances of this constructor also gather information
about the call stack that existed when the exception was created, a
so-called _stack trace_. This information is stored in the `stack`
property and can be helpful when trying to debug a problem: it
tells us the precise function where the problem occurred and which
other functions led up to the call that failed.
(((exception handling)))Note that the function `look` completely
ignores the possibility that `promptDirection` might go wrong. This is
the big advantage of exceptions—error-handling code is necessary only
at the point where the error occurs and at the point where it is
handled. The functions in between can forget all about it.
Well, almost...
== Cleaning up after exceptions ==
(((exception handling)))(((cleaning up)))(((withContext
function)))(((dynamic scope)))Consider the following situation: a
function, `withContext`, wants to make sure that, during its
execution, the top-level variable `context` holds a specific context
value. After it finishes, it restores this variable to its old value.
// include_code
[source,javascript]
----
var context = null;
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}
----
What if `body` raises an exception? In that case, the call to
`withContext` will be thrown off the stack by the exception, and
`context` will never be set back to its old value.
(((block)))(((try keyword)))(((finally keyword)))There is one more
feature that `try` statements have. They may be followed by a
`finally` block either instead of or in addition to a `catch`
block. A `finally` block means “No matter _what_ happens, run this
code after trying to run the code in the `try` block”. If a function
has to clean something up, the cleanup code should usually be put into
a `finally` block.
// include_code
[source,javascript]
----
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}
----
(((withContext function)))Note that we no longer have to store the
result of `body` (which we want to return) in a variable. Even if we
return directly from the `try` block, the `finally` block will be run.
Now we can do this and be safe:
// test: no
[source,javascript]
----
try {
withContext(5, function() {
if (context < 10)
throw new Error("Not enough context!");
});
} catch (e) {
console.log("Ignoring: " + e);
}
// → Ignoring: Error: Not enough context!
console.log(context);
// → null
----
Even though the function called from `withContext` exploded,
`withContext` itself still properly cleaned up the `context` variable.
== Selective catching ==
(((uncaught exception)))(((exception handling)))(((JavaScript
console)))(((developer tools)))(((call stack)))(((error)))When an
exception makes it all the way to the bottom of the stack without
being caught, it gets handled by the environment. What this means
differs between environments. In browsers, a description of the error
typically gets written to the JavaScript console (reachable through
the browser's Tools or Developer menu).
(((crash)))(((error handling)))For programmer mistakes or problems
that the program cannot possibly handle, just letting the error go
through is often okay. An unhandled exception is a reasonable way to
signal a broken program, and the JavaScript console will, on modern
browsers, provide you with some information about which function calls
were on the stack when the problem occurred.
(((user interface)))For problems that are _expected_ to happen during
routine use, crashing with an unhandled exception is not a very
friendly response.
(((syntax)))(((function,application)))(((exception handling)))(((Error
type)))Invalid uses of the language, such as referencing a nonexistent
((variable)), looking up a property on `null`, or calling something
that's not a function, will also result in exceptions being raised.
Such exceptions can be caught just like your own exceptions.
(((catch keyword)))When a `catch` body is entered, all we know is that
_something_ in our `try` body caused an exception. But we don't know
_what_, or _which_ exception it caused.
(((exception handling)))JavaScript (in a rather glaring omission)
doesn't provide direct support for selectively catching exceptions:
either you catch them all or you don't catch any. This makes it very
easy to _assume_ that the exception you get is the one you were
thinking about when you wrote the `catch` block.
(((promptDirection function)))But it might not be. Some other
((assumption)) might be violated, or you might have introduced a bug
somewhere that is causing an exception. Here is an example, which
_attempts_ to keep on calling `promptDirection` until it gets a valid
answer:
// test: no
[source,javascript]
----
for (;;) {
try {
var dir = promtDirection("Where?"); // ← typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}
----
(((infinite loop)))(((for loop)))(((catch keyword)))(((debugging)))The
`for (;;)` construct is a way to intentionally create a loop that
doesn't terminate on its own. We break out of the loop only when a
valid direction is given. _But_ we misspelled `promptDirection`,
which will result in an “undefined variable” error. Because the
`catch` block completely ignores its exception value (`e`), assuming
it knows what the problem is, it wrongly treats the variable error as
indicating bad input. Not only does this cause an infinite loop, but
it also “buries” the useful error message about the misspelled
variable.
As a general rule, don't blanket-catch exceptions unless it is for the
purpose of “routing” them somewhere—for example, over the network to
tell another system that our program crashed. And even then, think
carefully about how you might be hiding information.
(((exception handling)))So we want to catch a _specific_ kind of
exception. We can do this by checking in the `catch` block whether the
exception we got is the one we are interested in and by rethrowing it
otherwise. But how do we recognize an exception?
Of course, we could match its `message` property against the ((error))
message we happen to expect. But that's a shaky way to write code—we'd
be using information that's intended for human consumption (the
message) to make a programmatic decision. As soon as someone changes
(or translates) the message, the code will stop working.
(((Error type)))(((instanceof operator)))Rather, let's define a new
type of error and use `instanceof` to identify it.
// include_code
[source,javascript]
----
function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";
----
(((throw keyword)))(((inheritance)))The prototype is made to derive
from `Error.prototype` so that `instanceof Error` will also return
true for `InputError` objects. It's also given a `name` property
since the standard error types (`Error`, `SyntaxError`,
`ReferenceError`, and so on) also have such a property.
(((call stack)))The assignment to the `stack` property tries to give
this object a somewhat useful ((stack trace)), on platforms that
support it, by creating a regular error object and then using that
object's `stack` property as its own.
(((promptDirection function)))Now `promptDirection` can throw such an
error.
// include_code
[source,javascript]
----
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}
----
(((exception handling)))And the loop can catch it more carefully.
// test: no
[source,javascript]
----
for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Not a valid direction. Try again.");
else
throw e;
}
}
----
(((debugging)))This will catch only instances of `InputError` and let
unrelated exceptions through. If you reintroduce the typo, the
undefined variable error will be properly reported.
== Assertions ==
(((assert function)))(((assertion)))(((debugging)))_Assertions_ are a
tool to do basic sanity checking for programmer errors. Consider this
helper function, `assert`:
[source,javascript]
----
function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
if (!test)
throw new AssertionFailed(message);
}
function lastElement(array) {
assert(array.length > 0, "empty array in lastElement");
return array[array.length - 1];
}
----
(((validation)))(((run-time
error)))(((crash)))(((assumption)))(((array)))This provides a
compact way to enforce expectations, helpfully blowing up the program
if the stated condition does not hold. For instance, the `lastElement`
function, which fetches the last element from an array, would return
`undefined` on empty arrays if the assertion was omitted. Fetching the
last element from an empty array does not make much sense, so it is
almost certainly a programmer error to do so.
(((assertion)))(((debugging)))Assertions are a way to make sure
mistakes cause failures at the point of the mistake, rather than
silently producing nonsense values that may go on to cause trouble in
an unrelated part of the system.
== Summary ==
Mistakes and bad input are facts of life. Bugs in programs need to be
found and fixed. They can become easier to notice by having automated
test suites and adding assertions to your programs.
Problems caused by factors outside the program's control should
usually be handled gracefully. Sometimes, when the problem can be
handled locally, special return values are a sane way to track them.
Otherwise, exceptions are preferable.
Throwing an exception causes the call stack to be unwound until the
next enclosing `try/catch` block or until the bottom of the stack.
The exception value will be given to the `catch` block that catches
it, which should verify that it is actually the expected kind of
exception and then do something with it. To deal with the
unpredictable control flow caused by exceptions, `finally` blocks can
be used to ensure a piece of code is _always_ run when a block
finishes.
== Exercises ==
=== Retry ===
(((primitiveMultiply (exercise))))(((exception handling)))(((throw
keyword)))Say you have a function `primitiveMultiply` that, in 50 percent of
cases, multiplies two numbers, and in the other 50 percent, raises an
exception of type `MultiplicatorUnitFailure`. Write a function that
wraps this clunky function and just keeps trying until a call
succeeds, after which it returns the result.
(((catch keyword)))Make sure you handle only the exceptions you
are trying to handle.
ifdef::interactive_target[]
// test: no
[source,javascript]
----
function MultiplicatorUnitFailure() {}
function primitiveMultiply(a, b) {
if (Math.random() < 0.5)
return a * b;
else
throw new MultiplicatorUnitFailure();
}
function reliableMultiply(a, b) {
// Your code here.
}
console.log(reliableMultiply(8, 8));
// → 64
----
endif::interactive_target[]
!!hint!!
(((primitiveMultiply (exercise))))(((try keyword)))(((catch
keyword)))(((throw keyword)))The call to `primitiveMultiply` should
obviously happen in a `try` block. The corresponding `catch` block
should rethrow the exception when it is not an instance of
`MultiplicatorUnitFailure` and ensure the call is retried when it is.
To do the retrying, you can either use a loop that breaks only when a
call succeeds—as in the link:08_error.html#look[`look` example]
earlier in this chapter—or use ((recursion)) and hope you don't get a
string of failures so long that it overflows the stack (which is a
pretty safe bet).
!!hint!!
=== The locked box ===
(((locked box (exercise))))Consider the following (rather contrived)
object:
// include_code
[source,javascript]
----
var box = {
locked: true,
unlock: function() { this.locked = false; },
lock: function() { this.locked = true; },
_content: [],
get content() {
if (this.locked) throw new Error("Locked!");
return this._content;
}
};
----
(((private property)))(((access control)))It is a ((box)) with a
lock. Inside is an array, but you can get at it only when the box is
unlocked. Directly accessing the `_content` property is not allowed.
(((finally keyword)))(((exception handling)))Write a function called
`withBoxUnlocked` that takes a function value as argument, unlocks the
box, runs the function, and then ensures that the box is locked again
before returning, regardless of whether the argument function returned
normally or threw an exception.
ifdef::interactive_target[]
[source,javascript]
----
function withBoxUnlocked(body) {
// Your code here.
}
withBoxUnlocked(function() {
box.content.push("gold piece");
});
try {
withBoxUnlocked(function() {
throw new Error("Pirates on the horizon! Abort!");
});
} catch (e) {
console.log("Error raised:", e);
}
console.log(box.locked);
// → true
----
For extra points, make sure that if you call `withBoxUnlocked` when
the box is already unlocked, the box stays unlocked.
endif::interactive_target[]
!!hint!!
(((locked box (exercise))))(((finally keyword)))(((try keyword)))This
exercise calls for a `finally` block, as you probably guessed. Your
function should first unlock the box and then call the argument function
from inside a `try` body. The `finally` block after it should lock the
box again.
To make sure we don't lock the box when it wasn't already locked,
check its lock at the start of the function and unlock and lock
it only when it started out locked.
!!hint!!