JS magic Hall: mmDeferred source code analysis
I. preface the increasing influence of aveon. js, And the mmDeferred, one of the sub-modules, must be another stop in asynchronous call mode learning! This article will record my understanding of mmDeferred. If you have any questions, please correct them. Thank you. For details about the project, see mmDeferred @ github 2. API description {Deferred} Deferred ({Function | Object} mixin ?) Create a Deferred instance. When the mixin type is Object, append the mixin attributes and functions to the Promise instance of the Deferred instance. {String} state (): obtains the status of the current Deferred instance, including pending, fulfilled, and rejected. The conversion relationships are pending-> fulfilled and pending-rejected. {Promise} then ({Function} resolvefn ?, {Function} rejectfn ?, {Function} policyfn ?, {Function} ensurefn ?) To add four types of callback functions to the current Deferred instance and return a new Promise instance. Resolvefn is called when the instance status is changed to fulfilled, while rejectfn is called when the instance status is changed to rejected, the policyfn is equivalent to the progressHandler in the Promises/A + specification and has nothing to do with the instance status. You only need to call the policy function and call policyfn, ensurefn is used to simulate the finally statement blocks of the current Deferred instance for executing resolvefn, rejectfn, and policyfn. ensurefn is executed no matter which of the preceding three functions is executed. {Promise} otherwise ({Function} rejectfn ?) To add the rejectfn callback function to the current Deferred instance and return a new Promise instance. {Promise} ensure ({Function} ensurefn ?) To add the ensurefn callback function to the current Deferred instance and return a new Promise instance. {Undefined} resolve (... [*]) is used to trigger the fulfill callback-that is, to trigger a request to call the resolvefn function of the current Deferred instance, which can be called only once. {Undefined} reject (... [*]) is used to trigger the reject callback-that is, to trigger a request to call the rejectfn function of the current Deferred instance, which can only be called once. {Undefined} notify (... [*]) is used to trigger the notify callback-that is, to trigger a request to call the policyfn function of the current Deferred instance, which can be called multiple times. {Promise} Deferred. all (... [Promise]) requires multiple Promise objects to be passed in. When they are triggered normally, the resolve callback is executed. It is equivalent to jQuery's when method, but all is more standard and is a function recognized by the community. {Promise} Deferred. any (... [Promise]) requires multiple Promise objects to be passed in. The Promise object that is triggered normally will be executed and its resolve callback will be executed. Iii. source code analysis first, we need to know that there are two sets of Deferred and Promise operations in mmDeferred (the two operate on the same data structure instance). Promise is used to add four types of callback functions to the instance, the Deferred API is used to initiate an operation that changes the instance status or triggers a callback function call. It is restricted that only the Deferred operation set is returned through the Deferred function, while other APIs return the Promise operation set. In addition, it is worth noting the following points: 1. mmDeferred implements the instance status transition by calling the callback function before modifying the instance status. 2. the implementation of resolve, reject, and so on is not a unified method of asynchronous call to execute the callback function, but a synchronous execution of the callback function when the instance has been added to the callback function, when no callback function is added, an asynchronous call is initiated to give the currently executed code block the opportunity to add a callback function to the instance; 3. callback functions of the following methods are not supported for late binding: var deferred = Deferred () deferred. resolve () setTimeout (function () {deferred. promise. then (function () {console. log ('Hello World')}, 0) in the code structure, it is worth noting that: 1. using Variable declaration in JS to automatically improve the feature of hoist, the external interface is separated from the specific implemented code through the pre-return Statement. 2. extract the commonalities of resolve, reject and other functions to the private function _ fire, provide the commonalities of then, otherwise and other functions to the private function _ post, and provide the Deferred. all and Deferred. any is common to the private function some to avoid repeated code, thus greatly reducing the amount of code. To be improved, I think the _ fire and _ post functions should be removed from the Deferred function, and the instance attributes should be obtained and modified by replacing the closure with the external variables, the new _ fire and _ post functions will not be re-declared every time the Deferred function is called. If the status of instance A is pending, the status of instance A remains unchanged after the notify callback function is executed. When the ensure function is executed Subsequently, an exception is thrown, the reject method of instance B in the linked list will be called, resulting in the status of instance B being rejected, but the status of instance A is still pending. At this time, the resolve or reject method of instance B will not trigger the execution of the corresponding callback function, however, you can call the resovle or reject method of instance A to execute the corresponding callback functions of instance A and instance B. The following is the source code define ("mmDeferred", ["aveon"], function (aveon) {var noop = function () {} function Deferred (mixin) {var state = "pending" // identifies whether a callback function has been added. dirty = false function OK (x) {state = "fulfilled" return x} function ng (e) {state = "rejected" // pass the exception back to throw e} // Deferred instance var dfd = {callback: {resolve: OK, reject: ng, callback Y: noop, ensure: noop}, dirty: function () {return dirty }, State: function () {return state}, promise: {then: function () {return _ post. apply (null, arguments)}, otherwise: function (onReject) {return _ post (0, onReject)}, ensure: function (onEnsure) {return _ post (0, 0, 0, onEnsure)}, _ next: null} if (typeof mixin = "function") {mixin (dfd. promise)} else if (mixin & typeof mixin = "object") {for (var I in mixin) {if (! Dfd. promise [I]) {dfd. promise [I] = mixin [I] }}} "resolve, reject, y ". replace (/\ w +/g, function (method) {dfd [method] = function () {var that = this, args = arguments if (that. dirty () {// If a callback function has been added, call _ fire immediately. call (that, method, args)} else {// if no callback function is added, an asynchronous call is initiated so that the subsequent part of the current code block has enough time to add the callback function Deferred. nextTick (function () {_ fire. call (that, method, args)}) }}) return dfd/** highlights: * Because JS will declare Variables Automatic Upgrade (hoist) to the header of the code block * so the private method is written after the return statement to better format the code structure * // Add the callback function to the current Deferred instance function _ post () {var index =-1, fns = arguments; "resolve, reject, sort y, ensure ". replace (/\ w +/g, function (method) {var fn = fns [++ index]; if (typeof fn = "function ") {dirty = true if (method = "resolve" | method = "reject ") {// encapsulate the Deferred instance status modification function into the callback function // that is, call the function back to the function before modifying the instance status dfd. callback [method] = functio N () {try {var value = fn. apply (this, arguments) state = "fulfilled" return value} catch (err) {state = "rejected" return err }}} else {dfd. callback [method] = fn ;}}) // create the next Deferred instance var deferred = dfd of the linked list. promise. _ next = Deferred (mixin) return deferred. promise;} function _ fire (method, array) {var next = "resolve", value if (this. state () = "pending" | method = "Running y") {var fn = This. callback [method] try {value = fn. apply (this, array);} catch (e) {// handle the exception value = e} if (this. state () = "rejected") {next = "reject"} else if (method = "notify ") {next = "Y"} array = [value]} var ensure = this. callback. ensure if (noop! = Ensure) {try {ensure. call (this) // simulate finally} catch (e) {next = "reject"; array = [e] ;}} var nextDeferred = this. promise. _ next if (Deferred. isPromise (value) {// If the callback function returns a Deferred instance, insert the instance into the value before nextDeferred. _ next = nextDeferred} else {if (nextDeferred) {_ fire. call (nextDeferred, next, array) ;}}} window. deferred = Deferred; Deferred. isPromise = function (obj) {return !! (Obj & typeof obj. then = "function") ;}; function some (any, promises) {var deferred = Deferred (), n = 0, result = [], end function loop (promise, index) {promise. then (function (ret) {if (! End) {result [index] = ret // ensure the callback sequence n ++; if (any | n> = promises. length) {deferred. resolve (any? Ret: result); end = true }}, function (e) {end = true deferred. reject (e) ;}}for (var I = 0, l = promises. length; I <l; I ++) {loop (promises [I], I)} return deferred. promise;} Deferred. all = function () {return some (false, arguments)} Deferred. any = function () {return some (true, arguments)} Deferred. nextTick = aveon. nextTick return Deferred })