Asynchronous programming is very important in JavaScript. Too many asynchronous programming have problems with callback nesting, and this article provides some ways to fix "callback Hell".
setTimeout(function () { console.log(‘延时触发‘);}, 2000);fs.readFile(‘./sample.txt‘, ‘utf-8‘, function (err, res) { console.log(res);});
The above is a typical callback function, whether in the browser or in node, JavaScript itself is single-threaded, so in order to deal with some of the problems caused by single-threaded, asynchronous programming is a very important part of JavaScript.
Whether it is the most common in the browser Ajax, event monitoring, or node file read, network programming, database and other operations, can not be separated from asynchronous programming. In asynchronous programming, many operations are placed in the callback function (callback). A mix of synchronous and asynchronous, too many callback nesting will make the code difficult to understand and maintain, which is often criticized by people.
Let's look at the following code
fs.readFile(‘./sample.txt‘, ‘utf-8‘, (err, content) => { let keyword = content.substring(0, 5); db.find(`select * from sample where kw = ${keyword}`, (err, res) => { get(`/sampleget?count=${res.length}`, data => { console.log(data); }); });});
First we read the keyword in a file keyword
, and then according to the keyword
database query, and finally request data based on the results of the query.
It contains three asynchronous operations:
- File read: Fs.readfile
- Database query: Db.find
- HTTP request: Get
As you can see, we add a bit more nesting of callback functions without adding an asynchronous request, and the nesting of three async functions in this code has begun to make it difficult to read and maintain a piece of code that can be explicitly spoken.
Abstract this code will become the following:
asyncFunc1(opt, (...args1) => { asyncFunc2(opt, (...args2) => { asyncFunc3(opt, (...args3) => { asyncFunc4(opt, (...args4) => { // some operation }); }); });});
A triangular indent area appears on the left, and too many callbacks let us fall into the "callback Hell". The next step is to introduce some methods to circumvent the callback hell.
First, Disassembly function
One important problem with callback nesting is that code is difficult to read and maintain. Because of the widespread, excessive indentation (nesting) can greatly affect the readability of the code. Based on this, you can make one of the simplest optimizations--to disassemble each step into a singlefunction
functionGetData (count) {Get (${count} ', data = {console.log (data); });} function queryDB (kw) {db.find ( ' select * from sample where kw = ${kw} ', ( Err, res) = {GetData (res.length);}); function readFile (filepath" {fs.readfile (filepath, ' Utf-8 ', (err, content) = {let keyword = content.substring (0, 5); QueryDB ( keyword); });} ReadFile (/sample.txt ');
As you can see, the code is much clearer by the way it was rewritten. This method is very simple and has some effect, but lacks generality.
Second, event release/monitor mode
If you write an event listener in addEventListener
your browser, you must be familiar with the mode of posting/listening to this event.
Using this idea, on the one hand, we can listen to an event, when the event occurs, the corresponding callback operation, on the other hand, when certain operations are completed, the callback is triggered by publishing the event. This allows you to decouple the code that was originally bundled together.
Const EVENTS =Require' Events ');const eventemitter = new events. Eventemitter (); Eventemitter.on ( ' db ', (err, kw) = {Db.find (${kw} ', (err, res) = = {Eventemitter ( ' get ', res.length);}); Eventemitter.on ( ' get ', (err, count) = {get ( "/sampleget?) Count=${count} ', data = {console.log (data);}); Fs.readfile ( ' Utf-8 ', (err, content) = { Span class= "Hljs-keyword" >let keyword = content.substring (0, 5) ; Eventemitter. Emit ( ' db ', keyword);
Implementations that use this pattern require a library of event publishing/listening. The above code uses the node native events
module, of course you can use any library you like.
Third, Promise
Promise
is an asynchronous solution that was first proposed and implemented by the community and later written into the ES6 specification.
Currently, some mainstream browsers have natively implemented Promise
APIs that can be used to view browser support in can I use. Of course, if you want to do browser compatibility, consider using some Promise
of the implementation libraries, such as Bluebird, Q, and so on. Let's take Bluebird as an example:
First, we need to rewrite the Async method to Promise
Use the Bluebird method for a callback function that conforms to the node specification (the first argument must be error) promisify
. The method receives a standard asynchronous method and returns an Promise
object.
const bluebird = require(‘bluebird‘);const fs = require("fs");const readFile = bluebird.promisify(fs.readFile);
In this way, readFile
it becomes an Promise
object.
However, some async methods cannot be converted, or we need to use native Promise
, which requires some manual modification. Here is a way to retrofit.
fs.readFile
as an example, use native Promise
to transform the method:
const readFile = function (filepath) {let resolve, reject; let promise = new Promise ( (_resolve, _reject) = {resolve = _resolve; reject = _reject;}); let deferred = {resolve, Reject, promise}; Fs.readfile (filepath, ' Utf-8 ', function (err, ... args" {if (err) {deferred.reject (err);} else {deferred.resolve (... args);}); return deferred.promise;}
We create an object in the method Promise
and use it to change the state of the object in the asynchronous callback according to different circumstances reject
resolve
Promise
. The method returns the Promise
object. Other asynchronous methods can also be modified in this way.
It is assumed that through the transformation, and readFile
queryDB
the getData
method will return an Promise
object. The code becomes:
readFile(‘./sample.txt‘).then(content => { let keyword = content.substring(0, 5); return queryDB(keyword);}).then(res => { return getData(res.length);}).then(data => { console.log(data);}).catch(err => { console.warn(err);});
As you can see, the previous nested operation was programmed through then
the chained operation of the connection. There is a big improvement in the cleanliness of the code.
4 generator
generator
is a new syntax in ES6. function
Adding a * After a keyword changes the function to a generator
.
const gen = function* () { yield 1; yield 2; return 3;}
Execution generator
will return a Walker object that iterates through generator
the internal state.
let g = gen();g.next(); // { value: 1, done: false }g.next(); // { value: 2, done: false }g.next(); // { value: 3, done: true }g.next(); // { value: undefined, done: true }
It can be seen that the generator
function has one of the greatest characteristics, can be in the process of internal execution of the control of the program, yield
equivalent to play a role in a pause, and when under certain circumstances, the external control over the transfer back.
Imagine that we used generator
to encapsulate the code, use the keyword at the asynchronous task yield
, generator
give the program execution to the other code, and after the asynchronous task completes, invoke the next
method to restore yield
the execution of the code below. Take ReadFile as an example, the approximate process is as follows:
//our main task--display keyword // Use yield to temporarily interrupt the code below execution //yield followed by promise object const Showkeyword = Span class= "hljs-function" >function* (filepath) {console.log ( ' start reading '); let keyword = yield readFile (filepath); console.log (" keywords are ${filepath} ');} //generator Process Control let gen = Showkeyword (); let res = Gen.next (); Res.value.then (res = Gen.next (res))
In the main task section, the original readFile
asynchronous part becomes a similar synchronous notation, and the code becomes very clear. In the lower half, it is the process control that is given when a control is required to be handed back generator
.
However, we need a manual control generator
process that is more practical if it can be executed automatically- generator
automatically handing over control when needed.
To do this, we can use the Co library. It can be a code that eliminates our generator
control of the process
const co = reuqire ( ' co '); //our main task--display keyword //use yield to temporarily interrupt code execution below //yield followed by promise object const Showkeyword = function* (filepath) { console.log ( ' start reading '); let keyword = yield readFile (filepath); console.log (" keywords are ${filepath} ');} //using Coco (Showkeyword);
Which, yeild
after the keyword needs to be functio
,, promise
generator
, array
or object
. Can rewrite the first example of the article:
const co = reuqire(‘co‘);const task = function* (filepath) { let keyword = yield readFile(filepath); let count = yield queryDB(keyword); let data = yield getData(res.length); console.log(data);});co(task, ‘./sample.txt‘);
Wu, async/await
As you can see, the above method solves the problem of callback in asynchronous programming to some extent. However
- Function splitting is actually just splitting blocks of code, which is often detrimental to subsequent maintenance;
- The event publishing/Listening mode blurs the process relationship between asynchronous methods;
Promise
Although multiple nested asynchronous calls can be manipulated through a chained API, too much of then
it increases the redundancy of the Code, and it has some interference with the asynchronous tasks in each phase of the code.
generator
Although it can provide a good grammatical structure, but after all, generator
with yield
the context of how much is not quite appropriate.
So here's another way to do this, which is the async/await in Es7.
A brief introduction to Async/await. Basically, any function can be an async function, and the following are all valid forms of writing:
async function foo () {};const foo = async function () {};const foo = async () => {};
async
statements can be used in functions await
. await
is usually an Promise
object.
async function foo () { console.log(‘开始‘); let res = await post(data); console.log(`post已完成,结果为:${res}`);};
When the above function is executed, it await
can be simply understood that the function hangs, waits for await
the Promise
return, and executes the following statement.
It is worth noting that the code for this asynchronous operation looks like a "synchronous operation." This greatly facilitates the writing and reading of asynchronous code. Let's rewrite our example below.
const printData = async function (filepath) { let keyword = await readFile(filepath); let count = await queryDB(keyword); let data = await getData(res.length); console.log(data);});printData(‘./sample.txt‘);
As you can see, the code is simple and clear, and the asynchronous code has the structure of the "synchronous" code.
Note that readFile
both, queryDB
and getData
methods need to return an Promise
object. This can be rewritten in the way that is provided in the third part Promise
.
Postscript
Asynchronous programming, as part of JavaScript, has a very important position, which helps us avoid thread blocking caused by synchronous code, and also poses some difficulties for coding and reading. Too much callback nesting can easily get us into "callback hell" and make the code mess. To solve the "callback hell", we can use these five common methods as described in the article:
- Function disassembly
- Event Publish/Subscribe mode
- Promise
- Generator
- Async/await
Understanding the principles and implementation of various methods, and understanding the pros and cons, can help us to better asynchronous programming.
Alienzhou
Links: HTTP://WWW.JIANSHU.COM/P/BC7B8D542DCD
Source: Pinterest
JavaScript asynchronous programming Some solutions to "callback Hell"