JavaScript Promises and Error Handling

Errors in asynchronous code typically require a messy number of if else checks and a careful inspection of parameter values. Promises allow asynchronous code to apply structured error handling. When using promises, you can pass an error handler to the then method or use a catch method to process errors. Just like exceptions in regular code, an exception or rejection in asynchronous code will jump to the nearest error handler.

As an example, let’s use the following functions which log the execution path into a string variable.

var log = "";
 
function doWork() {
    log += "W";
    return Promise.resolve();
}
 
function doError() {
    log += "E";
    throw new Error("oops!");
}
 
function errorHandler(error) {
    log += "H";
}

We’ll use these functions with the following code.

doWork()
    .then(doWork)
    .then(doError)
    .then(doWork) // this will be skipped
    .then(doWork, errorHandler)
    .then(verify);
     
  function verify() {
    expect(log).toBe("WWEH");
    done();
}

The expectation is that the log variable will contain “WWEH” when the code finishes executing, meaning the flow of calls with reach doWork, then doWork, then doError, then errorHandler. There are two observations to make about this result, one obvious, one subtle.

The first observation is that when the call to doError throws an exception, execution jumps to the next rejection handler (errorHandler) and skips over any potential success handlers. This behavior is obvious once you think of promises as a tool to transform asynchronous code into a procedural flow of method calls. In synchronous code, an exception will jump over statements and up the stack to find a catch handler, and the asynchronous code in this example is no different.

What might not be immediately obvious is that the verify function will execute as a success handler after the error. Just like normal execution can resume in procedural code after a catch statement, normal execution can resume with promises after a handled error. Technically, the verify function executes because the error handler returns a successfully resolved promise. Remember the then method always returns a new promise, and unless the error handler explicitly rejects a new promise, the new promise resolves successfully.

A promise object also provides a catch method to handle errors. The last code sample could be written with a catch statement as follows. ]

doWork()
    .then(doWork)
    .then(doError)
    .then(doWork) 
    .then(doWork)
    .catch(errorHandler)
    .then(verify);

The catch method takes only a rejection handler method. There can be a difference in behavior between the following two code snippets:

.then(doWork, errorHandler)

… and …

.then(doWork)
.catch(errorHandler)

In the first code snippet, if the success handler throws an exception or rejects a promise, execution will not go into the error handler since the promise was already resolved at this level. With catch, you can always see an unhandled error from the previous success handler.

Finally, imagine you have a rejected promise in your code, but there is no error handler attached. You can simulate this scenario with the following line of code.

Promise.reject("error!");

Some native environments and promise polyfills will warn you about unhandled promise rejections by displaying a message in the console of the developer tools. An unhandled promise rejection means your application could be missing out on a critical error!

Karma Is Not Just For AngularJS

The AngularJS team created Karma, but Karma isn’t tied to AngularJS. As a test runner, I can use Karma to run tests against any JavaScript code using a variety of testing frameworks in a variety of browsers. All I need is Node.js.

Pretend I am writing code to represent a point in two dimensional space. I might create a file point.js.

var Point = function(x,y) {
    this.x = x;
    this.y = y;
}

I’ll test the code using specifications in a file named pointSpecs.js.

describe("a point", function() {
    it("initializes with x and y", function() {
        var p1 = new Point(3,5);
         
        expect(p1.x).toBe(3);
        expect(p1.y).toBe(5);
    });
});

What Karma can do is:

- Provide web browsers with all of the HTML and JavaScript required to run the tests I’ve written.

- Automate web browsers to execute all the tests

- Tell me if the tests are passing or failing

- Monitor the file system to re-execute tests whenever a file changes.

Setup

The first step is using npm to install the Karma command line interface globally, so I can run Karma from anywhere.

npm install karma-cli –g

Then I can install Karma locally in the root of my project where the Point code resides.

npm install karma --save-dev

Karma requires a configuration file so it knows what browsers to automate, and which files I’ve authored that I need Karma to load into the browser. The easiest way to create a basic configuration file is to run karma init from the command line. The init command will walk you through a series of questions to create the karma.conf.js file. Here is a sample session.

>karma init 
 
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine 
 
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no 
 
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
> Chrome
> 
 
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> js/**/*.js
> specs/**/*.js
> 
 
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
> 
 
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes 
 
Config file generated at "C:\temp\testjs\karma.conf.js".

I can open the config file to tweak settings and maintain the configuration in the future. The code is simple.

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      'js/**/*.js',
      'specs/**/*.js'
    ],
    exclude: [
    ],
    preprocessors: {
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS', 'Chrome'],
    singleRun: false
  });
};

Note that when I enter the location of the source and test files, I want to enter the location of the source files first. It’s helpful if you can organize source and test files into a directory structure so you can use globbing (**) patterns, but sometimes you need to explicitly name individual files to control the order of loading.

At this point I can start Karma, and the tests will execute in two browsers (Chrome, which I can see, and PhantomJS, which is headless). I’d probably tweak the config file to only use Phantom in the future.

AngularJS Abstractions: Scope

The parameter to the controller function is named $scope.

var AboutController = function($scope) {
 
    // ...    
 
};

The name $scope is important since it allows the AngularJS dependency injector to know what type of object you are asking for, in this case an object that will contain the view model. Any plain old JavaScript you attach to $scope (properties, functions, objects, arrays) is eligible to use from the expressions inside the view.

In the last post our model was relatively “flat” in the sense that properties and functions were added directly to $scope:

$scope.rabbitCount = 2;
 
$scope.increase = function() {
    $scope.rabbitCount *= $scope.rabbitCount;
};

This allowed us to use rabbitCount and increase() in the markup that was inside the view scope (the div) of the AboutController:

<div data-ng-controller="AboutController">
 
    <div>Number of rabbits in the yard: {{rabbitCount}}</div>
 
    <button ng-click="increase()">More rabbits</button>
     
</div>

You can think of $scope as the execution context for the expressions in the view. Saying ng-click=”increase” results in a call to $scope.increase. The only thing tricky to understand about $scope is that having a controller inside a controller, or a controller inside an application (which you’ll always have), will result in nested $scopes, and a nested $scope will prototypally inherit from it’s parent scope by default. This is why $scope is injected by angularJS – the framework sets up the prototype chain before giving your controller the $scope object to use as a model.

Inheritance means a view has access to it’s own scope as well as any inherited scope. In the following example, the view inside the ChildController markup can use an expression like {{rabbitCount}}, and this expression will read the rabbitCount property of AboutController’s scope ($scope.rabbitCount will follow the prototype chain).

<div data-ng-controller="AboutController">    
 
    <div>Number of rabbits in the yard: {{rabbitCount}}</div>
     
    <div data-ng-controller="ChildController">
        Number of rabbits in the yard: {{rabbitCount}}
        Number of squirrels in the yard: {{squirrelCount}}
    </div>
 
</div>

The one place to be careful with the inherited scope is with 2 way data binding. The way JavaScript prototypes work is that writing to the rabbitCount property of the ChildController $scope will add a rabbitCount property to the ChildController $scope and effectively hide the parent property. $scope.rabbitCount no longer needs to follow the prototype chain to find a value. More details and pictures on this scenario in “The Nuances of Scope Prototypal Inheritance”.