Analysis of jsdeferred principles in JavaScript asynchronous programming

Source: Internet
Author: User
Recently, I was looking at situ zhengmei's JavaScript framework design. I saw that the chapter of asynchronous programming introduced the jsdeferred library. I found it interesting. I spent a few days studying the code, here we will share with you. Asynchronous programming is a very important idea for writing Javascript. Especially when dealing with complex applications, asynchronous programming skills are crucial. Let's take a look at this milestone asynchronous programming library.

1. Preface

Recently, I was looking at situ zhengmei's JavaScript framework design. I saw that the chapter of asynchronous programming introduced the jsdeferred library. I found it interesting. I spent a few days studying the code, here we will share with you.

Asynchronous programming is a very important idea for writing Javascript. Especially when dealing with complex applications, asynchronous programming skills are crucial. Let's take a look at this milestone asynchronous programming library.

2. API source code parsing

2.1 Constructor

The safe constructor is used here to avoid errors when the new constructor is not called. Two methods are provided to obtain the Deferred object instance.

Function Deferred () {return (this instanceof Deferred )? This. init (): new Deferred ();} // method 1 var o1 = new Deferred (); // method 2var o2 = Deferred ();

2.2 Deferred. define ()

This method can be used to encapsulate an object, specify the object method, or expose the Deferred object method to the global scope.

Deferred. methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"]; /* @ Param obj assign the Deferred attribute Method to the object. @ Param list specifies the attribute Method */Deferred. define = function (obj, list) {if (! List) list = Deferred. methods; // obtain the global scope technique. if (! Obj) obj = (function getGlobal () {return this}) (); // mount all attributes to obj for (var I = 0; I <list. length; I ++) {var n = list [I]; obj [n] = Deferred [n];} return Deferred;} this. deferred = Deferred;

2.3 Asynchronous Operation implementation

In JSDeferred, there are many Asynchronous Operation implementation methods, which are also the most brilliant part of this framework. The methods are as follows:

Script. onreadystatechange (for IE5.5 ~ 8)

Img. onerror/img. onload (Asynchronous Operation Method for modern browsers)

For the node environment, use process. nextTick to Implement Asynchronous calling (outdated)

SetTimeout (default)

It selects the fastest API Based on the browser.

Use the onreadystatechange event of the script. Note that the browser has a limit on the number of concurrent requests (IE5.5 ~ 8 is 2 ~ 3, IE9 + and modern browsers are 6). When the number of concurrent requests exceeds the upper limit, the request initiation will be queued for execution, resulting in more serious latency. The idea of the code is to take Ms as a cycle. Each cycle starts with the asynchronous execution initiated through setTimeout. Other asynchronous execution operations within the cycle are implemented through script requests, if this method is frequently called, it indicates that it is more likely to reach the maximum number of concurrent requests. Therefore, you can reduce the cycle time, for example, set it to 100 ms to avoid high latency caused by queuing.

Deferred. next_faster_way_readystatechange = (typeof window = "object") & (location. protocol = "http :")&&! Window. opera & \ bMSIE \ B /. test (navigator. userAgent) & function (fun) {var d = new Deferred (); var t = new Date (). getTime (); if (t-arguments. callee. _ prev_timeout_called <150) {var cancel = false; // because the readyState keeps changing, avoid repeated execution of var script = document. createElement ("script"); script. type = "text/javascript"; // send an incorrect url to quickly trigger callback and Implement Asynchronous script operations. src = "data: text/javascript,"; script. onreadystatechange = function () {If (! Cancel) {d. canceller (); d. call () ;}}; d. canceller = function () {if (! Cancel) {cancel = true; script. onreadystatechange = null; document. body. removeChild (script); // remove node}; // unlike img, the request document is sent only when it is added to the document. body. appendChild (script);} else {// record or reset start time arguments. callee. _ prev_timeout_called = t; // start to use setTimeoutvar id = setTimeout (function () {d for each cycle. call ()}, 0); d. canceller = function () {clearTimeout (id) };} if (fun) d. callback. OK = fun; return d ;}

Use the img method, and use the src attribute to report errors and bind event callbacks for asynchronous operations

Deferred. next_faster_way_Image = (typeof window = "object") & (typeof Image! = "Undefined ")&&! Window. opera & document. addEventListener) & function (fun) {var d = new Deffered (); var img = new Image (); var hander = function () {d. canceller (); d. call ();} img. addEventListener ("load", handler, false); img. addEventListener ("error", handler, false); d. canceller = function () {img. removeEventListener ("load", handler, false); img. removeEventListener ("error", handler, false);} // assign an incorrect URLimg value. src = "data: imag/png," + Math. random (); if (fun) d. callback. OK = fun; return d ;}

For the Node environment, process. nextTick is used for asynchronous calling.

Deferred.next_tick = (typeof process === 'object' &&typeof process.nextTick === 'function') && function (fun) {var d = new Deferred();process.nextTick(function() { d.call() });if (fun) d.callback.ok = fun;return d;};

The setTimeout method has a minimum trigger interval. In the old IE browser, the interval may be slightly longer (15 ms ).

Deferred. next_default = function (fun) {var d = new Deferred (); var id = setTimeout (function () {clearTimeout (id); d. call (); // call chain}, 0) d. canceller = function () {try {clearTimeout (id) ;}catch (e) {}}; if (fun) {d. callback. OK = fun;} return d ;}

The default order is

Deferred. next = Deferred. parse | // process IE Deferred. next_faster_way_Image | // Deferred. next_tick in modern browsers | // Deferred. next_default in node environment; // default behavior

Based on official JSDeferred data, next_faster_way_readystatechange and next_faster_way_Image are faster than the original setTimeout Asynchronous Method by more than 700%.

After reading the data, the browser versions are relatively old. In modern browsers, the performance improvement should not be so obvious.

2.4 Prototype Method

The Deferred prototype method implements

_ Id is used to determine whether it is a Deferred instance. It seems that Mozilla has a plug-in called Deferred, so it cannot be detected through instanceof. Cho45 then customized the flag location for detection and submitted fxxking Mozilla on github.

Init initialization, attaches the _ next and callback attributes to each instance

Next is used to register the call function, which is implemented in the form of a linked list internally. The node is a Deferred instance, and the internal method called _ post

Error is used to register the error message when the function call fails, which is consistent with the internal implementation of next.

Call invokes next call chain

Fail invokes the error call chain

Cancel executes the cancel callback, which is valid only before calling the call chain. (The call chain is unidirectional and cannot be returned after execution)

Deferred. prototype = {_ id: 0xe38286e381ae, // identify whether it is the identifier of the Instance init: function () {this. _ next = null; // implementation of a linked list this. callback = {OK: Deferred. OK, // The default OK callback ng: Deferred. ng // callback when an error occurs}; return this;}, next: function (fun) {return this. _ post ("OK", fun); // call _ post to create a linked list}, error: function (fun) {return this. _ post ("ng", fun); // call _ post to create a linked list}, call: function (val) {return this. _ fire ("OK", val); // call chain next}, fail: function (err) {return this. _ fire ("ng", err); // call link that invokes error}, cancel: function () {(this. canceller | function (){}). apply (this); return this. init (); // reset}, _ post: function (okng, fun) {// create a linked list this. _ next = new Deferred (); this. _ next. callback [okng] = fun; return this. _ next;}, _ fire: function (okng, fun) {var next = "OK"; try {// The registered callback function may throw an exception, use try-catch to capture value = this. callback [okng]. call (this, value) ;}catch (e) {next = "ng"; value = e; // pass the error message if (Deferred. onerror) Deferred. onerror (e); // callback in which an error occurs} if (Deferred. isDeferred (value) {// determine whether it is a Deferred instance // The code here is for Deferred. value. _ next = this. _ next;} else {// if not, continue executing if (this. _ next) this. _ next. _ fire (next, value) ;}return this ;}}

2.5 auxiliary static method

In the code above, we can see some methods of the Deferred object (static method). The following is a brief introduction:

// The default successful callback Deferred. OK = function (x) {return x}; // default failure callback Deferred. ng = function (x) {throw x}; // judge the implementation of the instance based on _ id. isDeferred = function (obj) {return !! (Obj & obj. _ id = Deferred. prototype. _ id );}

2.6 Summary

Here, we need to stop and look at a simple example to understand the entire process.

The Defferred object has the next attribute method, and the next method is also defined in the prototype. Pay attention to this. For example, the following code:

var o = {};Deferred.define(o);o.next(function fn1(){    console.log(1);}).next(function fn2(){    console.log(2);});

O. next () is the attribute method of the Deffered object. This method returns an instance of the Defferred object. Therefore, next () is the next method on the prototype.

The first next () method converts the subsequent code into an asynchronous operation, and the next () method is used to register and call a function.

In the asynchronous operation of the first next (), call chain (d. call (). In other words, fn1 and fn2 are synchronously executed.

If we want both fn1 and fn2 to be executed asynchronously, rather than synchronously, we need to use the Deferred. wait method.

2.7 wait & register

We can use wait to convert fn1 and fn2 into asynchronous execution. The Code is as follows:

Deferred.next(function fn1() {    console.log(1)}).wait(0).next(function fn2() {    console.log(2)});

The wait method is very interesting. In the Deferred prototype, there is no wait method, but it is found on the static method.

Deferred. wait = function (n) {var d = new Deferred (), t = new Date (); // use a timer to convert it into an Asynchronous Operation var id = setTimeout (function () {d. call (new Date ()). getTime ()-t. getTime () ;}, n * 1000); d. canceller = function () {clearTimeout (id);} return d ;}

So how is this method put on the prototype? Originally, function conversion was performed through Deferred. register and bound to the prototype.

Deferred. register = function (name, fun) {this. prototype [name] = function () {// ke Lihua var a = arguments; return this. next (function () {return fun. apply (this, a) ;}};// register the method to the prototype Deferred. register ("wait", Deferred. wait );

We need to think about why we need to use this method to register the wait Method to the Deferred prototype object ?, Obviously, this method is a bit hard to understand.

Based on the examples, we can thoroughly understand the above problems.

Deferred.next(function fn1(){ // d1    console.log(1);}).wait(1) // d2.next(function fn2(){ // d3    console.log(2);});

This code first creates a call chain

Let's take a look at several key points in the execution process.

In the figure, d1, d2, d3, and d_wait indicate the instances of the Deferred object generated on the call chain.

After calling the d2 callback. OK indicates that the anonymous function of the wait () method is encapsulated, And the instance d_wait of the Deferred object generated in the wait () method is returned, Which is saved in the variable value and in the _ fire () method has an if judgment

if(Deferred.isDeferred(value)){    value._next = this._next;}
The call chain function is not executed yet, but a call chain is re-established. The chain header is d_wait, and setTimeout is used in the wait () method for asynchronous execution, use d. call () re-calls the call chain.

After understanding the entire process, it is better to return to the above questions. The reason for using register is that the wait method on the prototype does not directly use Deferred. wait, but Deferred. the wait method is used as a parameter to curry the next () method on the prototype, and then returns the next () method after colized. In fact, Deferred. wait () is similar to Deferred. next () in that Deferred performs the following operations asynchronously.

2.8 returns parallel

Imagine a scenario where we need multiple Asynchronous Network Query tasks, which have no dependencies and do not need to be differentiated. However, we need to wait until all the query results are returned before further processing, so what will you do? This scenario often appears in complex applications. If we use the following method (see Pseudo Code)

Var result = []; $. ajax ("task1", function (ret1) {result. push (ret1); $. ajax ("task2", function (ret2) {result. push (ret2); // perform operations });});

This method is acceptable, but it cannot send Task 1 and Task 2 at the same time (from the code point of view, there is a dependency between them, but it does not actually exist ). How can this problem be solved? This is the problem to be solved by Deferred. parallel.

Let's take a simple example to get a feel of this method of returning results.

Deferred.parallel(function () {    return 1;}, function () {    return 2;}, function () {    return 3;}).next(function (a) {    console.log(a); // [1,2,3]});

After the parallel () method is executed, the result is merged into an array and passed to callback. OK in next. We can see that parallel is a synchronous method. First, let's take a look at how the source code of parallel is implemented. Then, let's see if we can combine what we have learned to transform and implement the ajax effect we need.

Deferred. parallel = function (dl) {/* is used to process parameters. Three parameters can be received: 1. parallel (fn1, fn2, fn3 ). next () 2. parallel ({foo: $. get ("foo.html"), bar: $. get ("bar.html ")}). next (function (v) {v. foo // => foo.html data v. bar // => bar.html data}); 3. parallel ([fn1, fn2, fn3]). next (function (v) {v [0] // result of execution of fn1 v [1] // result of execution of fn2 v [3] // result returned by execution of fn3 }); */var isArray = false; // the first form of if (arguments. length> 1) {Dl = Array. prototype. slice. call (arguments); isArray = true; // other two forms, Array, class Array} else if (Array. isArray & Array. isArray (dl) | typeof dl. length = "number") {isArray = true;} var ret = new Deferred (), // Instance value of the Deferred object used for Merging Results = {}, // The result of collecting function execution num = 0; // counter. If the value is 0, all tasks are completed. // start traversal, the for-in method is not efficient here. for (var I in dl) {// prevents traversal of all attributes, such as if (dl such as toString. hasOwnProperty (I) {// use the closure to save the variable status (Function (d, I) {// use Deferred. next () starts an asynchronous task and collects the result if (typeof d = "function") dl [I] = d = Deferred. next (d); d. next (function (v) {values [I] = v; if (-- num <= 0) {// if the counter is 0, it indicates that all tasks have been completed. if (isArray) can be returned) {// if it is an array, the result can be converted to an array values. length = dl. length; values = Array. prototype. slice. call (values, 0);} // call parallel (). next (function (v) {}) to evoke the call chain ret. call (values );}}). error (function (e) {ret. f Ail (e) ;}); num ++; // counter plus 1}) (d [I], I) ;}// when the calculator is 0, if (! Num) {Deferred. next (function () {ret. call () ;}) ;}ret. canceller = function () {for (var I in dl) {if (dl. hasOwnProperty (I) {dl [I]. cancel () ;}}; return ret; // return Deferred instance };

Based on the above knowledge, we can use the Asynchronous Method in parallel. The Code is as follows:

Deferred. parallel (function fn1 () {var d = new Deferred (); $. ajax ("task1", function (ret1) {d. call (ret1) ;}); return d ;}, function () {var d = new Deferred (); $. ajax ("task2", function fn2 (ret2) {d. call (ret2)}); return d ;}). next (function fn3 (ret) {ret [0]; // => ret [1] returned by Task 1; // => result returned by Task 2 });

Why? Let's illustrate it and further understand it.

We use the if judgment in _ fire to establish a new call chain and obtain control of the de-counting function (I .e., the -- num in parallel), so that asynchronous methods can be executed in parallel.

Related Article

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.