官术网_书友最值得收藏!

  • Mastering Node.js
  • Sandro Pasquali
  • 1210字
  • 2021-07-21 18:17:14

Callbacks and errors

Members of the Node community develop new packages and projects every day. Because of Node's evented nature, callbacks permeate these codebases. We've considered several of the key ways in which events might be queued, dispatched, and handled through the use of callbacks. Let's spend a little time outlining the best practices, in particular about conventions for designing callbacks and handling errors, and discuss some patterns useful when designing complex chains of events and callbacks.

Conventions

Luckily, Node creators agreed upon sane conventions on how to structure callbacks early on. It is important to follow this tradition. Deviation leads to surprises, sometimes very bad surprises, and in general to do so automatically makes an API awkward, a characteristic other developers will rapidly tire of.

One is either returning a function result by executing a callback, handling the arguments received by a callback, or designing the signature for a callback within your API. Whichever situation is being considered, one should follow the convention relevant to that case:

  • The first argument returned to a callback function is any error message, preferably in the form of an error object. If no error is to be reported, this slot should contain a null value.
  • When passing a callback to a function it should be assigned the last slot of the function signature. APIs should be consistently designed this way.
  • Any number of arguments may exist between the error and the callback slots.

Note

To create an error object:

new Error("Argument must be a String!")

Know your errors

It is excellent that the Node community has automatically adopted a convention that compels developers to be diligent and report errors. However, what does one do with errors once they are received?

It is generally a very good idea to centralize error handling in a program. Often, a custom error handling system will be designed, which may send messages to clients, add to a log, and so on. Sometimes it is best to throw errors, halting the process.

Node provides more advanced tools for error handling. In particular, Node's domain system helps with a problem that evented systems have: how can a stack trace be generated if the full route of a call has been obliterated as it jumped from callback to callback?

The goal of domain is simple: fence and label an execution context such that all events that occur within it are identified as such, allowing more informative stack traces. By creating several different domains for each significant segment of your program, a chain of errors can be properly understood.

Additionally, this provides a way to catch errors and handle them, rather than allowing your entire Node process to collapse.

In the following example we're going to create two domains: appDomain and fsDomain. The goal is to be able to trace which part of our application is in an error state:

var domain = require("domain");
var fs = require("fs");

var fsDomain = domain.create();
fsDomain.on("error", function(err) {
  console.error("FS error", err);
});

var appDomain = domain.create();
appDomain.on('error', function(err) {
    console.log("APP error", err);
});

We now wrap the main program in appDomain, and the filesystem calls in fsDomain. We then create an error in fsDomain by trying to open a non-existent file:

appDomain.run(function() {
    process.nextTick(function() {
        fsDomain.run(function() {
            fs.open('no_file_here', 'r', function(err, fd) {
                if(err) {
                    throw err;
                }
                appDomain.dispose();
            });
        });
    });
});

When the preceding code executes, something resembling this should be echoed to the terminal:

FS error { [Error: ENOENT, open 'non-existent file']
  errno: 34,
  code: 'ENOENT',
  path: 'non-existent file',
  domain: 
   { domain: null,
     _events: { error: [Function] },
     _maxListeners: 10,
     members: [] },
  domainThrown: true }

Now let's create an error in appDomain by adding this code, which will produce a reference error (as no b is defined):

appDomain.run(function() {
    a = b;
    process.nextTick(function() {
...

An error similar to that in the precious code should be generated and reported by appDomain.

Notice the command appDomain.dispose. As maintaining these error contexts will consume some memory, it is best to dispose of them when no longer needed—after the code they contain has successfully executed, for example. We'll learn more advanced uses of this tool as we progress into more complex territories.

As an application grows in complexity it will become more and more useful to be able to trap errors and handle them properly, perhaps restarting only one part of an application when it fails rather than the entire system.

Building pyramids

Simplifying control flows has been a concern of the Node community since the very beginning of the project. Indeed, this potential criticism was one of the very first anticipated by Ryan Dahl, who discussed it at length during the talk in which he introduced Node to the JavaScript developer community.

Because deferred code execution often requires the nesting of callbacks within callbacks a Node program can sometimes begin to resemble a sideways pyramid, also known as "The Pyramid of Doom".

Accordingly, there are several Node packages available which take the problem on, employing strategies as varied as futures, fibers, even C++ modules exposing system threads directly. The reader is encouraged to experiment with these:

A more interesting general point is available here for us to consider regarding API choices in Node. Dahl might have reacted to this criticism by, for example, making one of the listed libraries part of Node's core, or indeed changing the entire way JavaScript is written. Instead, it was left to the community to determine the best practices, and to write the relevant packages. This is the Node way.

Note

Mikeal Rogers, in discussing why Promises were removed from the Node core, makes a strong argument in the following link for why leaving feature development to the community leads to a stronger core product:

http://www.futurealoof.com/posts/broken-promises.html

Considerations

Any developer is regularly making decisions with a far-reaching impact. It is very hard to predict all the possible consequences resulting from a new bit of code or a new design theory. For this reason, it may be useful to keep the shape of your code simple, and to force yourself to consistently follow the common practices of other Node developers. These are some guidelines you may find useful, as follows:

  • Generally, try to aim for shallow code. This type of refactoring is uncommon in non-evented environments—remind yourself of it by regularly re-evaluating entry and exit points, and shared functions.
  • Where possible provide a common context for callback re-entry. Closures are very powerful tools in JavaScript, and by extension, Node. As long as the context frame length of the enclosed callbacks is not excessive.
  • Name you functions. In addition to being useful in deeply recursive constructs, debugging code is much easier when a stack trace contains distinct function names, as opposed to anonymous.
  • Think hard about priorities. Does the order, in which a given result arrives or a callback is executed, actually matter? Importantly, does it matter in relation to I/O operations? If so, consider nextTick and setImmediate.
  • Consider using finite state machines for managing your events. State machines are (surprisingly) under-represented in JavaScript codebases. When a callback re-enters program flow it has likely changed the state of your application, and the issuing of the asynchronous call itself is a likely indicator that state is about to change.
主站蜘蛛池模板: 江阴市| 屯留县| 洛扎县| 增城市| 太谷县| 玉林市| 昌平区| 塔河县| 南平市| 册亨县| 辽源市| 琼海市| 喀什市| 义乌市| 从江县| 安多县| 历史| 开平市| 安泽县| 油尖旺区| 连云港市| 高密市| 德令哈市| 杂多县| 清苑县| 西乌珠穆沁旗| 百色市| 红原县| 卫辉市| 三河市| 普安县| 常州市| 泽普县| 乐业县| 湘潭市| 泗洪县| 个旧市| 桂平市| 伊金霍洛旗| 河西区| 桐柏县|