Learn JavaScript with me. Solving _javascript Techniques of asynchronous programming exception schemes

Source: Internet
Author: User
Tags error handling generator

One, JavaScript asynchronous programming two core difficulties

asynchronous I/O, event driven enables single-threaded JavaScript to perform network, file access without blocking the UI, and enables higher performance on the back end. However, there are some problems with the asynchronous style, the core of which is:

1, the function nesting too deep

The asynchronous invocation of JavaScript is based on a callback function, and when multiple asynchronous transactions are multilevel dependent, the callback function forms a multilevel nesting, and the code becomes
Pyramid type structure. This not only makes the code ugly and difficult to understand, but also makes debugging, refactoring process full of risk.

2. Exception handling

Callback nesting not only makes code messy, but also makes error handling more complex. This is mainly about exception handling.

Ii. Exception Handling

Like many fashionable languages, JavaScript also allows exceptions to be thrown and then captured with a Try/catch statement block. If the exception thrown is not captured, most JavaScript environments provide a useful stack trajectory. For example, the following code throws an exception because ' {' is an invalid JSON object.

function Jsontoobject (jsonstr) {return
 json.parse (JSONSTR);
}
var obj = jsontoobject (' {');
syntaxerror:unexpected end of input
//at object.parse (native)
//at jsontoobject (/asyncjs/stacktrace.js : 2:15)
//at object.<anonymous> (/asyncjs/stacktrace.js:4:11)

The stack trajectory not only tells us where the error was thrown, but also explains where the initial error occurred: line 4th code. Unfortunately, it is not always straightforward to trace the origin of asynchronous errors from top to bottom.

There are two kinds of situations where errors can be thrown in asynchronous programming: callback function errors, asynchronous function errors.

1. Callback function error

What happens if an error is thrown from an asynchronous callback? Let's do a test first.

settimeout (function A () {
 settimeout (function B () {
 settimeout (function C () {
  throw new Error (' Something Terrible has happened! ');
 }, 0;
 }, 0);
}, 0);

The result of the above application is an extremely short stack trajectory.

Error:something Terrible has happened!
At TIMER.C (/asyncjs/nestederrors.js:4:13)

Wait, what happened to A and b? Why don't they appear in the stack trajectory? This is because when you run C, the context of the asynchronous function no longer exists, and A and B are not in the memory stack. These 3 functions are run directly from the event queue. For the same reason, the use of Try/catch statement blocks does not capture errors thrown from asynchronous callbacks. In addition, the return in the callback function loses its meaning.

try {
 settimeout (function () {
 throw new Error (' Catch me if you can! ');
 }, 0;
} Catch (e) {
Conso Le.error (e);
}

Do you see the problem here? The Try/catch statement block here captures only those errors that occur within the settimeout function itself. Because SetTimeout runs its callback asynchronously, the error thrown by the callback will flow directly to the application even if the delay is set to 0.

In general, a function that takes an asynchronous callback is only useless if it is wrapped on a try/catch statement block. (The exception is that the asynchronous function does do something in sync and is prone to error.) For example, Node's Fs.watch (file,callback) is a function that throws an error if the destination file does not exist. Because of this, the callback in Node.js almost always accepts an error as its first argument, allowing the callback to decide how to handle the error.

2. Asynchronous function Error

Because asynchronous functions are returned immediately, errors that occur in asynchronous transactions cannot be captured by Try-catch and can only be resolved by scenarios where the caller provides an error-handling callback.

such as the common function (err, ...) in node. {...} The callback function is the contract that handles the error in node: Returns the error as the first argument of the callback function. The onerror function of the FileReader object in HTML5, for example, is used to handle errors in the process of reading files asynchronously.

For example, the following node application attempts to read a file asynchronously and is responsible for recording any errors (such as "file does not exist").

var fs = require (' FS ');
 Fs.readfile (' Fhgwgdz.txt ', function (err, data) {
 if (err) {return
 console.error (err);
 Console.log (data.tostring (' UTF8 '));

The client JavaScript library is slightly less consistent, but the most common pattern is to specify a separate callback for success or failure in both cases. The AJAX approach to JQuery follows this pattern.

$.get ('/data ', {
 success:successhandler,
 failure:failurehandler
});

Regardless of what the API looks like, always keep in mind that asynchronous errors originating from callbacks can only be handled within the callback.

Third, handling of exceptions not caught

If an exception is thrown from the callback, the person who invoked the callback is responsible for catching the exception. But what if the exception is never captured? At this point, different JavaScript environments have different rules of the game ...

1. In the browser environment

Modern browsers display those that are not caught in the developer console, and then return to the event queue. To modify this behavior, you can attach a processor to the window.onerror. If the Windows.onerror processor returns TRUE, the default error handling behavior of the browser can be blocked.

Window.onerror = function (err) {return
 true;//completely Ignore all errors
};

In a finished product application, some JavaScript error handling services, such as errorception, are considered. Errorception provides a ready-made Windows.onerror processor that reports all the outstanding exceptions to the application server, and the application server sends a message to inform us.

2. In the Node.js environment

In the node environment, the Window.onerror similarity is the Uncaughtexception event of the process object. Normally, the Node application exits immediately because of an unhandled exception. But as long as there's at least one Uncaughtexception event to deal with
, the Node application returns to the event queue directly.

Process.on (' Uncaughtexception ', function (err) {
 console.error (err);//Avoid the shutdown fate!
});

However, since node 0.8.4, the Uncaughtexception event has been discarded. According to its documentation, Uncaughtexception is a very crude mechanism for exception handling, not to use uncaughtexception, but to use domain objects.

What is the Domain object? You might ask that. The Domain object is an event object that converts throw into an ' error ' event. Here is an example.

var myDomain = require (' domain '). Create ();
Mydomain.run (function () {
 settimeout (function () {
 throw new Error (' Listen to me! ')
 };
});
Mydomain.on (' Error ', function (err) {
 console.log (' Error ignored! ');
});

The throw from the delay event simply triggers the error handler for the domain object.

Error ignored!

It's fascinating, isn't it? Domain objects let throw statements vivid a lot. The global exception handler should be regarded as the last straw, whether on the browser or the server side. Use it only when you are debugging.

Iv. several solutions

The following discussions on several solutions focus on the two core issues mentioned above, and of course consider other factors to evaluate their pros and cons.

1, Async.js

The first is the very famous async.js in node, which is able to expose itself in node, I'm afraid, thanks to the node's unified error-handling conventions.
In the front end, there is no such uniform agreement at first, so using async.js may require encapsulation of existing libraries.

Async.js is actually adding a layer of packaging to several common usage patterns for callback functions. For example, we need three asynchronous operations that are back and forth, using the pure callback function as follows:

Asyncopa (A, B, (err, result) => {
 if (err) {
 Handleerrora (err);
 }
 ASYNCOPB (c, result, (err, result) => {
 if (err) {
  handleerrorb (err);
 }
 ASYNCOPB (d, result, (err, result) => {
  if (err) {
  handlererrorc (err);
  }
  Finalop (result);
 });
 });

If we use the Async library to do:

Async.waterfall ([
 (CB) => {
 Asyncopa (A, B, (err, result) => {
  CB (err, C, result);
 });
 (c, Lastresult, CB) => {
 ASYNCOPB (c, Lastresult, (err, result) => {
  CB (err, D, result);
 })
 },
   
     (d, Lastresult, CB) => {
 Asyncopc (d, Lastresult, (err, result) => {
  CB (err, result);
 });
], (err, Finalresult) => {
 if (err) {
 handlererror (err);
 }
 Finalop (Finalresult);
});

   

As you can see, the callback function changes from the original horizontal development to the vertical development, while the error is uniformly passed to the final processing function.
The principle is that the function array of the latter function wrapped as the last parameter of the previous function CB afferent, while requiring:

Each function should execute its CB parameter; The first parameter of CB is used to pass the error. We can write a async.waterfall implementation of our own:

Let async = {
 Waterfall: (methods, FINALCB = _emptyfunction) => {
 if (!_isarray (methods)) {return
  FINALCB ( New Error (' argument to waterfall must is an array of functions ');
 }
 if (!methods.length) {return
  FINALCB ();
 }
 function wrap (n) {
  if (n = = methods.length) {return
  FINALCB;
  }
  return function (err, ... args) {
  if (err) {return
   FINALCB (err);
  }
  Methods[n] (... args, wrap (n + 1));
  }
 Wrap (0) (false);
 }
;

Async.js also has a variety of process control methods, such as series/parallel/whilst, to achieve common asynchronous collaboration.

Async.js's Problem:

There is still no escape from the callback function, only from the horizontal development to the vertical, or the programmer skilled asynchronous callback style.
There is still no use of try-catch and throw on error handling, depending on the convention that the first parameter of the callback function is used to pass the error.

2, Promise Program

ES6 's promise originates from promise/a+. There are several issues that need to be noted for asynchronous process control using promise.
To implement the previously mentioned functionality with promise, you need to wrap the asynchronous function so that it can return a promise:

function Topromisestyle (FN) {return
 (... args) => {return
 new Promise (Resolve, Reject) => {
  fn (...) Args, (err, result) => {
  if (err) reject (err);
  Resolve (result);})};}

This function converts an asynchronous function that conforms to the following rules to a function that returns promise:

The first parameter of the callback function is used to pass the error, and the second parameter is used to pass the normal result. You can then do the operation:

Let [OpA, OpB, OpC] = [Asyncopa, ASYNCOPB, Asyncopc].map (FN) => Topromisestyle (FN));

OpA (A, B)
 . Then ((res) => {return
 OpB (c, res);
 })
 . Then ((res) => {return
 OpC (d, RES);
 })
 . Then ((res) => {return
 finalop (res);
 })
 . catch ((Err) => {
 handleerror (err);
 });

Through promise, the original obvious asynchronous callback function style is more like the synchronous programming style, we only need to use the then method to pass the result, and return also has the corresponding significance:
Return in each then onfullfilled function (and onrejected) will set a value for the next then onfullfilled function (and onrejected) parameters.

In this way, return, Try-catch/throw can be used, but catch is in the form of methods, or not satisfactory.

3, generator Program

The generator introduced by ES6 can be understood as transferring control to other code in motion and returning functions that continue to execute when needed. Using generator can realize the function of the co-process.

By combining generator with promise, you can further convert asynchronous code into a synchronized style:

function* GetResult () {Let
 res, a, B, C, D;
 try {
 res = yield OpA (A, b);
 res = yield OpB (c, res);
 res = yield OpC (d);
 return res;
 } catch (Err) {return
 handleerror (err);
 }
}

However, we also need a function that can automatically run generator:

function spawn (Genf, ... args) {return to
 new Promise ((Resolve, reject) => {let
 gen = Genf (... args);

 Function Next (FN) {
  try {let
  r = fn ();
  if (r.done) {
   resolve (R.value);
  }
  Promise.resolve (R.value)
   . Then ((v) => {
   next () => {return
    gen.next (v);
   })
   . catch ((Err) => {
   Next (() => {return
    Gen.throw (err)}
   )
   ;
  } catch (Err) {
   reject (err);
  }

 Next (() => {return
  gen.next (undefined);
 });
 }


Use this function to invoke generator:

Spawn (GetResult)
 . Then ((res) => {
 finalop (res);
 })
 . catch ((Err) => {
 handlefinaloperror (err);
 });

It can be seen that try-catch and return have actually returned to the code in their original form, and there is no sign of the asynchronous style in the code.

Similar functions are implemented by co/task.js and other libraries.

4, ES7 of async/await

ES7 will introduce the Async function and the await keyword, and with this feature, we can easily write synchronized style code,
The original asynchronous I/O mechanism can still be leveraged.

With the async function, we can write the previous code like this:

Async function GetResult () {Let
 res, a, B, C, D;
 try {
 res = await OpA (A, b);
 res = await OpB (c, res);
 res = await OpC (d);
 return res;
 } catch (Err) {return
 handleerror (err);
 }
}

GetResult ();

And the Generator & Promise program does not look much different, but the keyword changed.
In fact, the async function is an official endorsement of the generator scheme as a built-in feature of the language.

Disadvantages of the Async function:

Await can only be used within async function, so once you have written several async function, or used a library that relies on async function, you will probably need more async function.

The async function, which is currently in the proposal phase, has not been supported by any browsers or node.js/io.js. The Babel transcoding device also needs to open the experimental option, and for browsers that do not support generator, it is also necessary to introduce a thick layer of regenerator runtime, which will take time to get applications in the front-end production environment.

The above is the entire content of this article, I hope to help you learn.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.