This article starts with the asynchronous style and analyzes the techniques, problems, and solutions that have become asynchronous in JavaScript. Specifically, from the problems caused by the callback, and talked about the use of events, Promise, generator and other technologies to solve these problems.
The non-blocking of async is infinitely good?
Async, is the lifeline of JavaScript without threading the model. On top of that, the Reactor
design pattern 1 is used.
JavaScript is all about "async" two sons. In both the browser environment and the node environment, most APIs separate the request (or message, call) from the return value (or the result) through an "event". and "events" can be separated from callbacks (Callback), for example,
var fs = require("fs");fs.readFile(__filename, function(e, data) { console.log("2. in callback");});console.log("1. after invoke");
The FS module encapsulates a complex IO module, whose invocation results are told by a simple callback to the caller. Looks pretty good, let's look at Ruby's EventMachine
:
require "em-files"EM::run do EM::File::open(__FILE__, "r") do |io| io.read(1024) do |data| puts data io.close end EM::stop endend
Because the APIs in Ruby's standard library are all synchronous, only third-party APIs like Eventmachine can be supported asynchronously. In the actual style, the two are similar, for our example, the JavaScript version seems to be more brief and does not need to add additional third-party modules.
asynchronous mode, which is less lossy than threading mode, and is even 2 better than Java in some scenarios. And, non-blocking
the API is node default, which makes Nodejs and its asynchronous callbacks a lot of applications.
For example, we want to find the dimensions of all the files in the current directory:
fs.readdir(__dirname, function(e, files) {//callback 1 if(e) { return console.log(e); } dirs.forEach(function(file) {//callback 2 fs.stat(file, function(e, stats) {//callback 3 if(e) { return console.log(e); } if(stats.isFile()) { console.log(stats.size); } }); });});
A very simple task creates a 3-level callback. In the early days of node application explosion, a lot of applications were born in this style. Obviously, this style of code has the following risks:
- Code is difficult to read and maintain: After nesting multiple callbacks, the author is not aware of the function hierarchy.
- Potential call stack consumption: in JavaScript, it's much easier than you think to go beyond the maximum stack. Many third-party modules do not make asynchronous calls, but pretend to support callbacks, and the stack is at greater risk.
- You want more? The first two bars are enough ...
Many programmers, because the first to abandon Nodejs, and even give up JavaScript. As for the second article, the elimination of various hidden bugs and the optimization of performance loss are beckoning to the programmer.
Wait, you said I kept talking about node, and I didn't mention the situation in the browser? Let's look at an example:
/*glboal $ */// we have jquery in the `window`$("#sexyButton").on("click", function(data) {//callback 1 $.getJSON("/api/topcis", function(data) {//callback 2 var list = data.topics.map(function(t) { return t.id + ". " + t.title + "/n"; }); var id = confirm("which topcis are you interested in? Select by ID : " + list); $.getJSON("/api/topics/" + id, function(data) {//callback 3 alert("Detail topic: " + data.content); }); });});
We try to get a list of articles and then give the user some interaction, let the user choose an article that they want to know more about, and continue to get the article details. In this simple example, 3 callbacks are generated.
In fact, the nature of asynchrony is the inherent style of the JavaScript language itself, regardless of the hosting environment. So, the problem with callbacks flying over the sky is the commonality of the JavaScript language.
Solution evented
JavaScript programmers may be one of the most creative of a bunch of programmers. There are a number of solutions to the callback problem. The most natural thing to think about is the use of event mechanisms.
Or the scene where the article was loaded before:
var topiccontroller = new Eventemitter (); Topiccontroller.list = function () {//a simple wrap for Ajax request $.getjson ("/api/topics", This.notify ("Topic:list")) ; return this;}; Topiccontroller.show = function (ID) {//a simple wrap for Ajax request $.getjson ("/api/topics/" + ID, this.notify ("topic : Show ", id)); return this;}; Topiccontroller.bind = function () {//bind DOM Events $ ("#sexyButton"). On ("Click", This.run.bind (this)); return this;}; Topiccontroller._querytopic = function (data) {var list = Data.topics.map (function (t) {return t.id + "." + t.t Itle + "/n"; }); var id = confirm ("which Topcis is interested in?" Select by ID: "+ list"; This.show (ID). Listento ("Topic:show", this._showtopic);}; Topiccontroller._showtopic = function (data) {alert (data.content);}; Topiccontroller.listento = function (eventName, listener) {//a helper method to ' bind ' This.on (EventName, Listener.bind ( this);}; Topiccontroller.notify = function (eventName) {//generatE a notify callback internally var = this, args; args = Array.prototype.slice (arguments, 1); return function (data) {args.unshift (data); Args.unshift (EventName); Self.emit.apply (self, args); };}; Topiccontroller.run = function () {this.list (). Lisento ("Topic:list", this._querytopic);};/ /kickoff$ (function () {Topiccontroller.run ();});
As you can see, the B-grid is much higher now. Various packages, all kinds of decoupling. First of all, in addition to universal jquery, we also rely on EventEmitter
, this is an observer pattern implementation of 3, such as Asyncly/eventemitter2. A simple summary of this style:
- Eliminates most scenarios where anonymous functions are used as callbacks, up to 0 nested, and the code is brief
- Between each state (or step), use an event mechanism to correlate
- Each step is independent of each other to facilitate future maintenance
If you are hard to be picky, there are shortcomings;
- Overall process blur due to excessive separation
- Increased code volume and another maintenance cost
Higher order functions
With higher-order functions, functions can be recursively executed sequentially and concurrently.
We can write a higher order function that allows the order of incoming functions to execute:
var runInSeries = function(ops, done) { var i = 0, next; next = function(e) { if(e) { return done(e); } var args = Array.prototype.slice.call(arguments, 1); args.push(next); ops[0].apply(null, args); }; next();};
Or our previous example:
var list = function(next) { $.getJSON("/api/topics", function(data) { next(null, data); });};var query = function(data, next) { var list = data.topics.map(function(t) { return t.id + ". " + t.title + "/n"; }); var id = confirm("which topcis are you interested in? Select by ID : " + list); next(null, id);};var show = function(id, next) { $.getJSON("/api/topics/" + id, function(data) { next(null, data); });};$("#sexyButton").on("click", function() { runInSeries([list, query, show], function(e, detail) { alert(detail); });});
It still looks good, concise and clear, and the final amount of code does not increase. If you like this way, take a look at Caolan/async and find out more.
PROMISE
A promise represents the eventual result of an asynchronous operation. The primary-interacting with a promise is through it then method, which registers callbacks to receive either a PR Omise ' s eventual value or the reason why the promise cannot is fulfilled.
Apart from genteel's interpretation, promise is an abstraction of a task. Promise's related APIs provide a set of methods and objects to implement this abstraction.
There are a number of implementations of promise:
- ECMAScript Promise4
- That is, the native Promise object, chrome32+ above support
- Promise/a+5 Standard
- kriskowal/q
- Cujojs/when
- Tildeio/rsvp.js
- Other vendor standards
Although the standards are many, all implementations basically follow the following basic rules:
- Promise Object
- is a finite state machine
- Complete (fulfilled)
- Negation (rejected)
- Wait (pending)
- End (settled)
- There must be a
then([fulfill], [reject])
way for users to handle successful failures individually.
- Optional
done([fn])
, fail([fn])
method
- Support Chain API
- Deffered Object
- Provide
reject
and resolve
methods to complete a promise
The author will introduce the concrete mechanism and realization of promise in the special article. Just a little here, use the basic, anywhere jquery to solve the async problem in the small scene before you:
$("#sexyButton").on("click", function(data) { $.getJSON("/api/topcis").done(function(data) { var list = data.topics.map(function(t) { return t.id + ". " + t.title + "/n"; }); var id = confirm("which topcis are you interested in? Select by ID : " + list); $.getJSON("/api/topics/" + id).done(function(done) { alert("Detail topic: " + data.content); }); });});
Unfortunately, using promise does not make the callback problem much better. In this scenario, the promise does not reflect its strength. Let's take a look at the examples in the official jquery documentation:
$.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ).done(function( a1, a2 ) { // a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively. // Each argument is an array with the following structure: [ data, statusText, jqXHR ] var data = a1[ 0 ] + a2[ 0 ]; // a1[ 0 ] = "Whip", a2[ 0 ] = " It" if ( /Whip It/.test( data ) ) { alert( "We got what we came for!" ); }});
Here, two AJAX requests are initiated, and the two promise are merged into one, and the developer only deals with this final promise.
For Q.js
example when.js
, or a third-party library that can support more complex features. will also make your code style a great change. It can be said that promise opens up new doors for dealing with complex processes, but it also has a cost. These complex packages have a considerable overhead of 6.
Geneartor
ES6 's generator introduces an yield
expression that makes process control more variable. Node-fiber lets us see what it coroutine
looks like in JavaScript.
var Fiber = require(‘fibers‘);function sleep(ms) { var fiber = Fiber.current; setTimeout(function() { fiber.run(); }, ms); Fiber.yield();}Fiber(function() { console.log(‘wait... ‘ + new Date); sleep(1000); console.log(‘ok... ‘ + new Date);}).run();console.log(‘back in main‘);
But imagine that if every JavaScript has this feature, then a variety of attempts by a normal JavaScript programmer will be challenged. Your object will be inexplicably changed by the code in another fiber.
In other words, there is no grammatical design that can be used to mix JavaScript code that supports fiber and does not support fiber and does not cause confusion. This non-portability of node-fiber makes coroutine less realistic in JavaScript 7.
But yield
it is a shallow coroutines, it can only stop the user code, and only if it GeneratorFunction
can be used yield
.
In another article, I have explained in detail how to use Geneator to solve the problem of asynchronous process.
Using the yield
implementation suspend
approach, we can get a very brief introduction to our previous problem solving:
$("#sexyButton").on("click", function(data) { suspend(function *() { var data = yield $.getJSON("/api/topcis"); var list = data.topics.map(function(t) { return t.id + ". " + t.title + "/n"; }); var id = confirm("which topcis are you interested in? Select by ID : " + list); var detail = yield $.getJSON("/api/topics/"); alert("Detail topic: " + detail.content); })();});
In order to use yield
, we also have a choice:
- Generator compatibility is not good, only the new version of node and chrome support
- Requires a lot of rewriting of the underlying framework, which is interface normalization (thunkify), to support
yield
some of the constraints
yield
The resulting code style may confuse some novices
- Multilayer
yield
generated stacks and difficult to debug
Conclusion
With so much to say, asynchronous programming, which is quite different from the threading model, allows more programmers to understand the charms of the node as it pops up. If the next time C or Java programmer says that the JavaScript callback is too ugly, please let him read this article!
Original: http://hao.jser.com/archive/4296/
ASYNC programing in Javascript[turn]