Learn how to use javascript to solve asynchronous programming exceptions. If you are interested, refer
I. Two core difficulties in JavaScript asynchronous programming
Asynchronous I/O and event-driven allow single-threaded JavaScript to execute the network and file access functions without blocking the UI, and enable high performance on the backend. However, asynchronous styles also lead to some troubles. The core problems are:
1. Function Nesting is too deep
JavaScript asynchronous calls are based on callback functions. When multiple asynchronous transactions depend on multiple levels, the callback function forms multi-level nesting and the code becomes
Pyramid structure. This not only makes the code difficult to understand, but also makes the debugging and restructuring process risky.
2. Exception Handling
Callback nesting not only makes code messy, but also makes error handling more complicated. Here we will talk about exception handling.
Ii. Exception Handling
Like many trendy languages, JavaScript can throw exceptions and capture them with a try/catch Block. If the thrown exception is not captured, most JavaScript environments provide a useful stack trace. 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. (/AsyncJS/stackTrace.js:4:11)
The stack track not only tells us where an error is thrown, but also shows the first error: 4th lines of code. Unfortunately, top-down tracking of asynchronous error origins is not so straightforward.
Asynchronous programming may throw errors in two ways: callback function errors and asynchronous function errors.
1. Callback Function Error
What will happen if an error is thrown from the asynchronous callback? Let's take 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 track.
Error: Something terrible has happened!at Timer.C (/AsyncJS/nestedErrors.js:4:13)
Wait. What happened to A and B? Why are they not in the stack trace? This is because when running C, the context of the asynchronous function does not exist, and A and B are not in the memory stack. These three functions run directly from the event queue. For the same reason, the try/catch Block cannot capture errors thrown from Asynchronous callbacks. In addition, the return in the callback function also loses its meaning.
try { setTimeout(function() { throw new Error('Catch me if you can!'); }, 0);} catch (e) {console.error(e);}
Have you seen the problem? Here, the try/catch Block only captures the internal errors of the setTimeout function. Because setTimeout runs the callback asynchronously, even if the latency is set to 0, the error thrown by the callback will directly flow to the application.
In general, functions that use Asynchronous callback are useless even if the try/catch Block is encapsulated. (In particular, this asynchronous function is indeed doing something synchronously and is prone to errors. For example, Node's fs. watch (file, callback) is a function that throws an error when the target file does not exist .) For this reason, the callback in Node. js almost always accepts an error as its first parameter, so that the callback itself can decide how to handle the error.
2. asynchronous function Error
Because asynchronous functions return immediately, errors in asynchronous transactions cannot be captured through try-catch. Only the caller can provide an error handling callback solution.
For example, the common function (err,...) {...} callback function in Node is the Convention for handling errors in Node: the error is returned as the first real parameter of the callback function. For example, the onerror function of the FileReader object in HTML5 is used to handle errors during asynchronous file reading.
For example, the Node Application below tries to asynchronously read a file and records any errors (such as "the 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 consistency of the client-side JavaScript library is slightly worse, but the most common mode is to define a separate callback for the two cases of success or failure. JQuery's Ajax method follows this pattern.
$.get('/data', { success: successHandler, failure: failureHandler});
Regardless of the API format, you must always remember that you can only process asynchronous errors originating from callbacks within the callback.
III,Uncaptured Exception Handling
If an exception is thrown from the callback, the person who calls the callback is responsible for capturing the exception. But what if exceptions are never caught? At this time, different JavaScript environments have different game rules ......
1. In the browser Environment
Modern browsers display uncaptured exceptions on the developer console and then return to the event queue. To modify this behavior, you can add a processor to window. onerror. If the windows. onerror processor returns true, the browser's default error handling behavior will be blocked.
Window. onerror = function (err) {return true; // ignore all errors completely };
In a finished application, some JavaScript error handling services, such as Errorception, are considered. Errorception provides a ready-made windows. onerror processor that reports all uncaptured exceptions to the application server and then sends a message to us.
2. In the Node. js Environment
In the Node environment, a similar thing of window. onerror is the uncaughtException event of the process object. Under normal circumstances, the Node application will immediately exit because of exceptions not captured. However, as long as there is at least one uncaughtException event to process
Node application will directly return the event queue.
Process. on ('uncaughtexception ', function (err) {console. error (err); // This avoids the shutdown fate !});
However, since Node 0.8.4, The uncaughtException event is discarded. According to the document, for exception handling, uncaughtException is a very crude mechanism. Do not use uncaughtException, but use Domain objects.
What is a Domain object? You may ask this question. The Domain object is an event object, which converts throw to an 'error' event. The following is an example.
var myDomain = require('domain').create();myDomain.run(function() { setTimeout(function() { throw new Error('Listen to me!') }, 50);});myDomain.on('error', function(err) { console.log('Error ignored!');});
The throw originating from the latency event simply triggers the error processor of the Domain object.
Error ignored!
It's amazing, isn't it? The Domain object makes the throw statement much more vivid. The global exception processor should be regarded as the last life-saving tool on both the browser and server. Use it only during debugging.
Iv. Several solutions
The following discussions on several solutions mainly focus on the two core issues mentioned above. Of course, we will also consider other factors to judge their advantages and disadvantages.
1. Async. js
The first is the well-known Async. js in Node. This library can be expanded in Node, probably thanks to the unified error handling conventions of Node.
At the front end, there was no such uniform convention at the beginning. Therefore, if you use Async. js, you may need to encapsulate the existing library.
Async. js adds a layer of packaging to several common usage modes of callback functions. For example, we need three asynchronous operations on the front and back dependencies. The pure callback function is written 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:
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);});
We can see that the callback function has changed from the original horizontal development to vertical development, and errors are uniformly transmitted to the final processing function.
The principle is to package the last function in the Function Array and pass it in as the last cb parameter of the previous function. At the same time, the following requirements are required:
Each function should execute its cb parameter; the first parameter of cb is used to pass errors. We can write an implementation of async. waterfall by ourselves:
let async = { waterfall: (methods, finalCb = _emptyFunction) => { if (!_isArray(methods)) { return finalCb(new Error('First argument to waterfall must be 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 multiple process control methods such as series, parallel, and whilst to implement common asynchronous collaboration.
Async. js problems:
The callback function has not been removed from the outside, but it has changed from horizontal development to vertical development. It still requires programmers to be proficient in asynchronous callback.
The above try-catch and throw are still not used in error handling, and it relies on a convention such as "the first parameter of the callback function is used to pass errors.
2. Promise Solution
The Promise of ES6 comes from Promise/A +. Using Promise for asynchronous process control involves several issues that need attention,
To implement the previously mentioned functions using Promise, we need to wrap the asynchronous function first 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 can convert asynchronous functions that comply with the following rules into functions that return Promise:
The first parameter of the callback function is used to pass errors, and the second parameter is used to pass normal results. Then you can perform the following operations:
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 Synchronous Programming style. We only need to use the then method to pass the result, and return also has the corresponding meaning:
The return value in every then onFullfilled function (and onRejected) is set for the parameters of the next then onFullfilled function (and onRejected.
In this way, both return and try-catch/throw can be used, but catch appears in the form of methods, which is still unsatisfactory.
3. Generator Solution
The Generator introduced by ES6 can be understood as transferring control to other Code while running, and returning the function to continue execution as needed. Generator can be used to implement the coroutine function.
By combining Generator with Promise, you can further convert asynchronous code into a synchronous 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 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 call 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 with their original appearance, and there is no trace of asynchronous style in the code form.
Similar functions are implemented in libraries such as co/task. js.
4. ES7 async/await
ES7 will introduce async function and await keywords. With this feature, we can easily write code in the synchronous style,
At the same time, the original asynchronous I/O mechanism can still be used.
With the async function, we can write the previous Code as follows:
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();
It does not seem very different from the Generator & Promise solution, but the keyword is changed.
In fact, The async function is an official recognition of the Generator solution and uses it as a language built-in function.
Disadvantages of async function:
Await can only be used within the async function. Therefore, once you write several async functions or use a library dependent on the async function, you may need more async functions.
Currently, the async function is not supported by any browser or Node. JS/io. js. The Babel Transcoder also needs to enable lab options. For browsers that do not support Generator, a thick layer of regenerator runtime needs to be introduced. It takes time to get applications in the front-end production environment.
The above is all the content of this article, hoping to help you learn.