Promise's past life and magical techniques

Source: Internet
Author: User

Browser event model and callback mechanism

JavaScript runs in the browser as a single thread, which is mentioned in every JavaScript textbook. The JavaScript and UI threads are also in the same thread because of the security considerations for UI threading operations. Therefore, for long time-consuming operations, the response of the UI will be blocked. For a better UI experience, you should try to avoid lengthy, time-consuming operations in JavaScript, such as a large number of object diff for loops, or long I/O blocking tasks. So most of the tasks in the browser are asynchronous (non-blocking) execution, such as: Mouse click events, window Size drag events, timer triggering events, Ajax completion callback events and so on. When each asynchronous event is completed, it is placed in an event pool called the browser event queue. These tasks, which are placed in the event pool, will be handled by one of the JavaScript engine single-threaded processes, and when the asynchronous task is met again in this process, they will be placed in the event pool, waiting for the next tick to be processed. In addition, a new component-web Worker is introduced in HTML5, which can perform these tasks outside of the JavaScript thread without blocking the current UI thread.

The event loop model in the browser looks like this:

Because of this internal event-looping mechanism in the browser, JavaScript always handles event tasks in a callback callback manner. So there is no avoiding the processing of multiple JavaScript asynchronous tasks, and will encounter "Callback Hell" (Callback Hell), making such code unreadable and difficult to maintain.

asyncTask1(data, function (data1){    asyncTask2(data1, function (data2){        asyncTask3(data2, function (data3){                // .... 魔鬼式的金字塔还在继续        });    });});
Promise was born

So many JavaScript cows began to look for patterns to solve this callback, and then promise (jquery's deferred Promise category) was introduced into the JavaScript world. Promise in English semantics is: "commitment", which means that when a calls a long task B, B will return a "commitment" to a,a do not care about the entire implementation process, continue to do their own tasks; When B is completed, it passes a, and a pre-agreed callback between a is executed. and deferred in English semantics is: "Delay", which also shows that promise solve the problem is a delay of the event, the event will be deferred to a future appropriate point of execution.

promise/a+ Specification:

    • The Promise object has three states: the initial state of the Pending–promise object, when the task is completed or rejected, fulfilled– the task completes and the status of the success, and the status of the rejected– task execution and failure;
    • The state of promise can only be transferred from "Pending" state to "fulfilled" state or "rejected" state, and cannot be reversed, while "fulfilled" state and "rejected" state cannot be converted to each other;
    • The Promise object must implement the then method, then the core of the promise specification, and then the then method must return a Promise object, and the same promise object can register multiple then methods, and the callback is executed in the same order as they were registered;
    • The then method accepts two callback functions, each of which are: callbacks at success and callbacks when they fail, and they are called when promise transitions from "Pending" state to "fulfilled" state, and in promise by "Pending" state to " Rejected "state is called.

As shown in the following:

According to the promise/a+ specification, the promise pseudo-code that we begin with in the article can be converted to the following code:

asyncTask1(data)    .then(function(data1){        return asyncTask2(data1);    })    .then(function(data2){       return asyncTask3(data2);    })    // 仍然可以继续then方法

Promise the original callback in the hell, from the horizontal increase cleverly changed to vertical growth. In a chain style, the portrait is written to make the code more readable and easy to maintain.

Promise is gradually accepted by everyone in the JavaScript world, so the standard version of ES6 has introduced the Promise specification. Now through the Babel, you can fully assured the introduction of product environment.

In addition, to solve this type of asynchronous task, in ES7 will introduce async、await two keywords, in a synchronous way to write asynchronous tasks, it is known as "the ultimate JavaScript asynchronous processing." These two keywords are a way of ES6 standard in the combination of the generator ( generator ) and the new syntax of the promise, built-in generator actuators. async、awaitThe explanation of course will not unfold in this article, and interested readers can refer to the MDN information.

The Magical magic of promise

As mentioned above, promise is a good choice when it comes to handling asynchronous callbacks or delaying the execution of a task. Below we will introduce some of the promise techniques (the following will take advantage of the angular $q and $http for example, of course for jquery deferred , ES6 Promise still practical):

Serial processing of multiple asynchronous tasks

The callback Hell case mentioned above is a result of an attempt to serially process multiple asynchronous tasks, allowing the code to continue to extend horizontally, with a sharp decline in readability and maintainability. Of course, we also mentioned that promise uses a chain-and-delay execution model to pull code from a horizontal extension back to vertical growth. The implementation in angular uses $http the following:

$http.get(‘/demo1‘) .then(function(data){     console.log(‘demo1‘, data);     return $http.get(‘/demo2‘, {params: data.result});  }) .then(function(data){     console.log(‘demo2‘, data);     return $http.get(‘/demo3‘, {params: data.result});  }) .then(function(data){     console.log(‘demo3‘, data.result);  });

Because promise can be passed, the continuation of the method can be continued then , or it can be changed to other promise or data in the way of vertical expansion. So in the example of $http can also be changed to other promise (such as $ timeout , $resource ...) )。

Parallel processing of multiple asynchronous tasks

In some scenarios, the number of asynchronous tasks we are dealing with is not as strong as the one in the previous example, but it is only necessary to execute some specific logic when this sequence of asynchronous tasks is complete. This time for performance considerations and so on, we do not need to serialize them all to execute, parallel execution of them will be an optimal choice. If the callback function is still in use, this is a very annoying problem. With promise, you can also solve it gracefully:

$q.all([$http.get(‘/demo1‘),        $http.get(‘/demo2‘),        $http.get(‘/demo3‘)]).then(function(results){    console.log(‘result 1‘, results[0]);    console.log(‘result 2‘, results[1]);    console.log(‘result 3‘, results[2]);});

This allows you to wait until a bunch of asynchronous tasks are complete and perform a specific business callback. The same principle applies to the routing mechanism ngRoute and uiRoute the resolve mechanism in angular: when the route is executed, the promise that gets the template, the promise of all resolve data are stitched together, and they are acquired in parallel, And then wait for them all to end before starting to initialize ng-view , ui-view the scope object of the instruction, and the Compile template node, and insert the page DOM, complete a routed jump and switch the view, the static HTML template into a dynamic page display.

The pseudo-code for the angular routing mechanism is as follows:

  var gettemplatepromise = function (options) {//...    Splicing all template or Templateurl}; var getresolvepromises = function (resolves) {//...    Stitching all Resolve}; var controllerloader = function (options, CurrentScope, Tplandvars, initlocals) {//... ctrlinstance = $cont        Roller (Options.controller, ctrllocals);        if (Options.controlleras) {Currentscope[options.controlleras] = ctrlinstance;    }//... return currentscope;    }; var templateandresolvepromise = $q. All ([Gettemplatepromise (Options)].concat (getresolvepromises (Options.resolve | |    {}))); Return Templateandresolvepromise.then (function resolvesuccess (tplandvars) {var currentscope = CurrentScope | | $roo        Tscope. $new ();        Controllerloader (Options, CurrentScope, Tplandvars, initlocals); ... compile & append dom});  

The use of this type of routing mechanism is also used in the Ng-trainning plugin in the blog post "custom angular plugin – Website User Guide". The specific analysis and application of this code will be in the following separate articles, please look forward to.

For promise processing of synchronous data, the unified calling interface

With promise, because the most asynchronous processing in front-end code is AJAX, they are packaged to promise. Then style. What about non-asynchronous processing of a subset of synchronizations? Methods such as Localstorage, SetTimeout, SetInterval and the like. In most cases, bloggers still recommend the use of promise packaging, so that the return interface of the project service is unified. This also facilitates serial, parallel processing of multiple asynchronous tasks, as in the previous example. This is also the case with the angular route for setting template only.

For settimeout, setinterval in angular has built the $timeout and $interval services for us, they are a promise package. What about Localstorage? The $q.when method can be used to directly wrap the return value of the Localstorage as the Promise interface, as follows:

    var data = $window.localStorage.getItem(‘data-api-key‘);    return $q.when(data);

The return value of the service layer for the entire project can be encapsulated in a uniform style used, and the project becomes more consistent and unified. It is also easy and consistent to use when multiple service is required to parallel or serial processing.

Promise DSL Semantic encapsulation for deferred tasks

As mentioned earlier, promise is deferred to the future to perform certain tasks, and at the time of invocation it returns a "promise" to the consumer, and the consumer thread is not blocked. After consumers accept the "promise", consumers will not have to care about how these tasks are done, and to urge the producers of the task execution status. By the end of the mission, the "promise" in the hands of consumers was fulfilled.

For such delay mechanisms, it is also extremely common in UI interactions in the front-end. For example, the modal window display, the user in the modal window interaction results can not be predicted in advance, the user is to click on the "OK" button, or "Cancel" button, this is a future delay event will occur. The processing of such scenes is also an area that promise is good at. The implementation of the modal in Angular-ui Bootstrap is also based on the Promise encapsulation.

$modal.open({    templateUrl: ‘/templates/modal.html‘,    controller: ‘ModalController‘,    controllerAs: ‘modal‘,    resolve: {    }})    .result    .then(function ok(data) {        // 用户点击ok按钮事件    }, function cancel(){        // 用户点击cancel按钮事件    });

This is because modal gives us a promise result object (commitment) in the return value of the Open method. When the user clicks the OK button in the modal window, the bootstrap will use $q to defer resolve perform the OK event, and conversely, if the user taps the Cancel button, $q the defer cancel event will be used reject .

This will be a good solution to the problem of delayed triggering, but also to avoid callback的地狱 . We can still further semantic its return value, naming it in terms of business-owned terminology to form a set of DSL APIs.

 function open(data){    var defer = $q.defer();    // resolve or reject defer;    var promise = defer.promise;    promise.ok = function(func){        promise.then(func);        return promise;    };    promise.cancel = function(func){        promise.then(null, func);        return promise;    };    return promise;};

We can access it in the following ways:

$modal.open(item)   .ok(function(data){        // ok逻辑   })   .cancel(function(data){       // cancel 逻辑   });

Does it feel more semantic? The return method of $http in angular success, error is also the same logic encapsulation. Registers the success registry function as a successful callback for the. Then method, and the registration method of the error registers as the failed callback for the then method. So the success and error methods are just the angular framework for the set of syntactic sugars we encapsulate on the promise syntax.

Angular the implementation code of the success, error callback:

  promise.success = function(fn) {    promise.then(function(response) {      fn(response.data, response.status, response.headers, config);    });    return promise;  };  promise.error = function(fn) {    promise.then(null, function(response) {      fn(response.data, response.status, response.headers, config);    });    return promise;  };
Using promise to implement pipeline AOP interception

In software design, AOP is the abbreviation of aspect-oriented programming, meaning: aspect-oriented programming. A decoupling design of the common function and the business module is realized by using the compile-time (Compile) implant code, the runtime dynamic agent, and the framework to provide a pipeline execution strategy. AOP is a continuation of OOP, a hotspot in software development, and one of the core contents of many service-side frameworks (such as spring in the Java World), which is a derivative of functional programming. AOP enables the isolation of parts of the business logic, which reduces the coupling between parts of the business logic, increases the reusability of the program, and improves development efficiency. AOP practical scenarios include: Permission control, log module, transaction processing, performance statistics, exception handling and other independent, general non-business modules. For more information about AOP, refer to http://en.wikipedia.org/wiki/Aspect-oriented_programming.

In the angular, there are also some AOP design ideas, which can be used to separate, decouple, unify and maintain the general function of the program and the business module. Interceptors (interceptors) and adorners ($provide. Decorator) in the $http are two common AOP pointcuts in angular. The former is implemented by a pipeline execution strategy, while the latter is implemented dynamically by the promise pipeline at runtime.

First look at the angular interceptor implementation:

 //Register an Interceptor service $provide.factory (' Myhttpinterceptor ', function ($q, Dependency1, Dependency2) {    return {//optional method ' request ': function (config) {///request to Process return config after successful; },//optional method ' Requesterror ': function (rejection) {//request failed after processing if (Canrecover (rejection)) {return res    Ponseornewpromise} return $q. Reject (rejection);    },//optional method ' response ': function (response) {//Return to the town to handle return response; },//optional method ' Responseerror ': function (rejection) {//Return failed processing if (canrecover (rejection)) {return res    Ponseornewpromise} return $q. Reject (rejection); }  };});/ /Register the service in the Interceptor chain $httpprovider.interceptors.push (' myhttpinterceptor ');//The Interceptor can also be registered as a factory method. But the previous method is more recommended. $httpProvider. Interceptors.push ([' $q ', function ($q) {return {' request ': function (config) {//Ibid}, ' Resp Onse ': function (response) {//Ibid.}}}]);  

This makes it possible to $http intercept Ajax requests in angular or in the same way $resource . But how does this interception approach be implemented within the angular? Angular uses the promise mechanism to form an asynchronous pipeline stream, placing real Ajax requests in the middle of the request, Requesterror, and response, responseerror pipelines, thus creating an interception of Ajax requests.

Its source code implementation is as follows:

var interceptorfactories = This.interceptors = [];var responseinterceptorfactories = This.responseinterceptors = [];this . $get = [' $httpBackend ', ' $browser ', ' $cacheFactory ', ' $rootScope ', ' $q ', ' $injector ', function ($httpBackend, $browser,    $cacheFactory, $rootScope, $q, $injector) {var defaultcache = $cacheFactory (' $http ');    var reversedinterceptors = []; ForEach (interceptorfactories, function (interceptorfactory) {Reversedinterceptors.unshift (isstring ( interceptorfactory)?    $injector. Get (interceptorfactory): $injector. Invoke (Interceptorfactory));    }); ForEach (responseinterceptorfactories, function (interceptorfactory, index) {var responsefn = isstring (interceptorfact Ory)?      $injector. Get (interceptorfactory): $injector. Invoke (Interceptorfactory); Reversedinterceptors.splice (index, 0, {response:function (response) {return Responsefn ($q. When (response)        ); }, Responseerror:function (response) {return Responsefn ($q. RejecT (response));    }      });    });  ... function $http (requestconfig) {... var chain = [Serverrequest, undefined];  var promise = $q. When (config); Apply Interceptors ForEach (reversedinterceptors, function (Interceptor) {if (Interceptor.request | | interceptor.req    Uesterror) {chain.unshift (interceptor.request, interceptor.requesterror); } if (Interceptor.response | | interceptor.responseerror) {chain.push (Interceptor.response, Interceptor.responseEr    ROR);  }  });    while (chain.length) {var thenfn = Chain.shift ();    var rejectfn = Chain.shift ();  Promise = Promise.then (THENFN, REJECTFN); } promise.success = function (fn) {Promise.then (function (response) {fn (response.data, Response.Status, Response.    Headers, config);    });  return promise;  }; Promise.error = function (fn) {promise.then (NULL, function (response) {fn (response.data, Response.Status, response    . headers, config);    });  return promise;  }; return promise;};

Immediately $get after the injection method, angular will interceptors responseInterceptors merge and invert into one reversedInterceptors of the interceptor internal variables to be saved. Finally, the $http function is [serverRequest, undefined] centered on (the serverRequest promise operation of the AJAX request), and reversedInterceptors all the interceptor functions are added to the chain chain array in turn. If it is request or requesterror, then it is placed in the starting position of the chain array, but in the case of response or responseerror, then it is placed at the end of the chain array.

Note that the request and requesterror that are added in chain, or response and responseerror, must be paired, in other words, a non-empty request may be registered with a undefined of requesterror, Or a request for undefined and a non-empty requesterror. Just like the declaration of the chain Array (var chain = [serverRequest, undefined];) , pairs of serverrequest and undefined objects are placed into the array. Because the code that follows will register these interceptor functions with the promise mechanism, implement the pipeline AOP interception mechanism.

In promise, two functions are required to register callbacks, respectively, for a successful callback and a failed callback. Here the request and response will be registered as promise's success callback, while Requesterror and Responseerror will be registered as promise's failure callback. So the request and Requesterror,response or Responseerror added in the chain are paired, in order to be able to simply register the promise callback function in the next loop. These registered interceptor chains, which will $q.when(config) start with promise, will first pass $http in the Config object and execute all the request interceptors, then to serverRequest this AJAX request, which will suspend all the response interceptors behind. Until the AJAX request response is completed, the rest of the response interceptor callback is executed sequentially, and if there is an exception during the request process, the Requesterror interceptor is executed. The failure of the AJAX request or the exception of the response interceptor that handles Ajax is also captured by the Responseerror interceptor that is registered later.

From the last two pieces of code can also learn about $http the service of the success method and error method, is angular for everyone to provide a convenient way to promise. The success method is to register an incoming success callback and an error callback for undefined, while the error is to register a null successful callback and an incoming failure callback.

Summarize

Written here, this article has also come to an end. I hope that we can have a certain understanding of promise, and can be "handy" in the actual project, enhance the readability and maintainability of the code. In the examples used in this article, you can find them in the bloggers ' jsbinhttp://jsbin.com/bayeva/edit?html,js,output.

In addition, it is also welcome to pay attention to the blogger's public number [broken Wolf] (QR code is located on the right side of the blog), here will be the time for everyone to push the blogger's latest blog, thank you for your support and encouragement.

Promise's past life and magical techniques

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.