Chapter 5. Your First Express Application

Now that we've got our feet wet building an Express application for Node.js, let's work on an application that performs a useful function. The application we'll build will keep a list of notes, and it will let us explore some aspects of a real application.

In this chapter, we'll only build the basic infrastructure of the application, and in the later chapters, we'll add features such as using different database engines to store the notes, user authentication, deployment on public servers, and other things.

ES-2015 Promises and Express router functions

Before we get into developing our application, we have another new ES-2015 feature to discuss—the Promise class. Promise objects are used for deferred and asynchronous computation. A Promise class represents an operation that hasn't completed yet, but is expected to be completed in the future.

In Chapter 1, About Node.js, we briefly discussed the Promise class while discussing Node.js's asynchronous execution model. Since Node.js was designed before the Promise class, the Node.js community follows a different convention for writing asynchronous code; this is the callback function. Those callback functions are called to perform any kind of deferred or asynchronous computation.

The Promise class approaches the same problem from a different angle. It has one large advantage in avoiding the so-called Pyramid of Doom that's inevitable with deeply nested callback functions.

Promises are just one way to work around this so-called callback hell. Over the years, several other approaches have been developed, some of which are very popular:

While the callback function approach to asynchronous programming has proved to be extremely flexible, we recognize there's a problem: the Pyramid of Doom. It's named after the shape the code takes after a few layers of nesting. Any multistage process can quickly escalate to code nested 15 levels deep.

Consider the following example:

router.get('/path/to/something', (req, res, next) => {
  doSomething(arg1, arg2, (err, data1) => {
    if (err) return next(err);
    doAnotherThing(arg3, arg2, data1, (err2, data2) => {
      if (err2) return next(err2);
      somethingCompletelyDifferent(arg1, arg42, (err3, data3) => {
        if (err3) return next(err3);
        doSomethingElse((err4, data4) => {
          if (err4) return next(err4);
          res.render('page', { data });
        });
      });
    });
  });
});

We're about to write some code that, in the old style of Node.js, would have looked something like the following code. But, with the Promise class, we can take this problem.

We generate a Promise this way:

exports.asyncFunction = function(arg1, arg2) {
  return new Promise((resolve, reject) => {
    // perform some task or computation that's asynchronous
    // for any error detected:
    if (errorDetected) return reject(dataAboutError);
    // When the task is finished
    resolve(any, results);
  });
};

Note

Note that asyncFunction is an asynchronous function, but it does not take a callback. Instead, it returns a Promise object, and the asynchronous code is executed within a callback passed to the Promise class.

Your code must indicate the asynchronous status via the resolve and reject functions. Your caller then uses the function as follows:

asyncFunction(arg1, arg2)
.then((any, results) => {
   // the operation succeeded
   // do something with the results
})
.catch(err => {
   // an error occurred
});

The system is fluid enough that the function passed in a .then can return something, such as another Promise, and you can chain the .then objects together. The .then and .catch add handler functions to the Promise object that you can think of as a handler queue.

The handler functions are executed one at a time. The result given by one .then function is used as the argument list of the next .then function.

Promises and error handling

Promise objects can be in one of three states:

  • Pending: This is the initial state, neither fulfilled nor rejected
  • Fulfilled: This is the final state where it executed successfully and produced a result
  • Rejected: This is the final state where execution failed

Consider this code segment similar to the one we'll use later in this chapter:

notes.read(req.query.key)
.then(note => { return filterNote(note); })
.then(note => { return swedishChefSpeak(note); })
.then(note => {
    res.render('noteview', {
        title: note ? note.title : "",
        notekey: req.query.key,
        note: note
    });
})
.catch(err => { next(err); });

There are several places where errors can occur in this little bit of code. The notes.read function has several possible failure modes: the filterNote function might want to raise an alarm if it detects a cross-site scripting attack. The Swedish chef could be on strike. There could be a failure in res.render or the template being used. But we have only one way to catch and report errors. Are we missing something?

The Promise class automatically captures errors, sending them down the chain of operations attached to Promise. If the Promise class has an error on its hands, it skips over the .then functions and will instead invoke the first .catch function it finds. In other words, using instances of Promise provides a higher assurance of capturing and reporting errors. With the older convention, error reporting was trickier, and it was easy to forget to add correct error handling.

Flattening our asynchronous code

The problem being addressed is that asynchronous coding in JavaScript results in the Pyramid of Doom. This coding pattern looks like simple ordinary function calls, ones that would execute in linear order from top to bottom. Instead, the pyramid is a nest of code that could execute in any order depending on the whims of event processing.

To explain, let's reiterate the example Ryan Dahl gave as the primary Node.js idiom:

db.query('SELECT ..etc..', function(err, resultSet) {
   if (err) {
      // Instead, errors arrive here
   } else {
      // Instead, results arrive here
    }
});
// We WANT the errors or results to arrive here

The purpose—to avoid blocking the event loop with a long operation—has an excellent solution here: to defer processing the results or errors until "later". But this solution created this pyramid-shaped problem.

If the resultSet requires asynchronous processing, there's another layer of callbacks and so on, until this becomes a little pyramid. The problem is that results and errors land in the callback. Rather than delivering them to the next line of code, the errors and results are buried.

Promise objects help flatten the code so that it no longer takes a pyramidal shape. They also capture errors, ensuring delivery to a useful location. But those errors and results are still buried inside an anonymous function and do not get delivered to the next line of code.

The ECMAScript Committee is working on a proposal for ES-2016 to add two new keywords, await and async, that should give us the ability to land asynchronous errors and results in the place they belong: the next line of code. Refer to the official documentation at https://tc39.github.io/ecmascript-asyncawait/ for details.

As proposed, you might be able to write the following code:

async UserFind(params) {
   return await dbLookupFunction(params);
}
var userData = async UserFind({ email });

Additional tools

While the Promise class is a big step forward, it doesn't solve everyone's needs. Additional libraries are becoming available with extra functionality or to wrap existing libraries so they return Promise objects.

Here are a few libraries to help you sail through commonly encountered scenarios:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset