[Node. js] simplifying asynchronous Process Control for learning everyauth

Source: Internet
Author: User

Example
First, let's take a look at this Code. The function is to read a url from a local file, request this url, and write the result to another file.

Var fs = require ('fs '),
Http = require ('http ');

Fs. readFile ('./url.txt', 'utf8', function (err, data ){
Http. get (data, function (res ){
Var body = '';
Res. on ('data', function (c ){
Body + = c;
}). On ('end', function (){
Fs. writeFile ('./fetchresult', data + body, function (e ){
If (e) console. log ('error', e );
Else console. log ('done ');
});
});
}). On ('error', function (e ){
Console. log (e );
});
});
This Code includes three steps and three functions, but it is coupled with each other, which is difficult to modify due to poor readability. To modify or add any part of the code, you must check the complete code, every callback is instantly extracted into a variable, and the entire process cannot be separated.

Ultimate Edition
In this case, everyauth uses a method to write the implementation code of the entire process as follows:

Engine
. Do ('fetchhtml ')
. Step ('readurl ')
. Accepts ('')
. Promises ('url ')
. Step ('gethtml ')
. Accepts ('url ')
. Promises ('html ')
. Step ('savehtml ')
. Accepts ('url html ')
. Promises (null)

. ReadUrl (function (){
// Read url from file
...
})
. GetHtml (function (url ){
// Send http request
...
})
. SaveHtml (function (url, html ){
// Save to file
...
})

. Start ('fetchhtml ')
Do is the beginning of a series of streamline methods. step specifies the method name corresponding to each step. promises indicates the variable name returned by this step, accepts indicates the parameters accepted in this step (the variables provided by the method in the previous step ). The next step is to implement each step in a chain.

The entire process is very clear, and the program's self-description is very good. It writes an asynchronous process in synchronous mode. To modify a step, you can directly locate the method corresponding to the step. You do not need to read the code of the entire process. To add a step, you only need to insert a new step in those step flows and implement the specific method. You can obtain any parameters provided by the previous step.

How it works
Four objects are used for implementation: promise/step/sequence/engine.

Promise is the foundation. I believe many people are familiar with its concept. Simply put, they put multiple callback in a promise object and call these callback in a proper place. The role here is to notify the queue to execute the next step when the step execution ends. Specifically, the function of the next step is saved to the promise of the previous step. When the previous step completes the task, it calls back the data to the next step for execution.

Step is responsible for executing a step, passing in the corresponding parameters, and saving the execution result (return Value) according to the specified promises name for the next step.

Sequence manages the step chain so that the registered step can be executed step by step.

The engine is an object that provides external interfaces. It manages and stores the steps and sequence in each do request, and uses the retriable method to dynamically add itself.

Var fs = require ('fs '),
Http = require ('http ');

Var Promise = function (values ){
This. _ callbacks = [];
This. _ errbacks = [];
If (arguments. length> 0 ){
This. fulfill. apply (this, values );
}
}
Promise. prototype = {
Callback: function (fn, scope ){
// If values already exists, it indicates that promise has fulfill and is executed immediately.
If (this. values ){
Fn. apply (scope, this. values );
Return this;
}
This. _ callbacks. push ([fn, scope]);
Return this;
}
, Errback: function (fn, scope ){
If (this. err ){
Fn. call (scope, this. err );
Return this;
}
This. _ errbacks. push ([fn, scope]);
Return this;
}
, Fulfill: function (){
If (this. isFulfilled | this. err) return;
This. isFulfilled = true;
Var callbacks = this. _ callbacks;
This. values = arguments;
For (var I = 0, l = callbacks. length; I <l; I ++ ){
Callbacks [I] [0]. apply (callbacks [I] [1], arguments );
}
Return this;
}
, Fail: function (err ){
Var errbacks = this. _ errbacks;
For (var I = 0, l = errbacks. length; I <l; I ++ ){
Errbacks [I] [0]. call (errbacks [I] [1], err );
}
Return this;
}
}

Var Step = function (name, _ engine ){
This. name = name;
This. engine = _ engine;
}

Step. prototype = {
Exec: function (seq ){
Var args = this. _ unwrapArgs (seq)
, Promises = this. promises

Var ret = this. engine [this. name] (). apply (this. engine, args );

// If the return value of a function is not Promise, that is, the direct return value in the function has no asynchronous operation. Encapsulate an immediate promise for consistent processes
Ret = (ret instanceof Promise)
? Ret
: This. engine. Promise ([ret]);

Ret. callback (function (){
Var returned = arguments
, Vals = seq. values;
// Write the return value to seq. values after step execution is complete for the next step.
If (promises! = Null) promises. forEach (function (valName, I ){
Vals [valName] = returned [I];
});
})

// Add the default error callback Method
Ret. errback (this. engine. errback (), this. engine );
Return ret;
}
, _ UnwrapArgs: function (seq ){
If (! This. accepts) return [];
Return this. accepts. reduce (function (args, accept ){
// Retrieve the corresponding variable based on the accept name
Args. push (seq. values [accept]);
Return args;
}, []);
}
}

Var Sequence = function (name, engine ){
This. name = name;
This. engine = engine;
This. stepNames = [];
This. values = {};
}

Sequence. prototype = {
_ Bind: function (priorPromise, nextStep ){
Var nextPromise = new Promise ()
, Seq = this;

PriorPromise. callback (function (){
Var resultPromise = nextStep.exe c (seq );
ResultPromise. callback (function (){
NextPromise. fulfill ();
});
});
Return nextPromise;
}

, Start: function (){
Var steps = this. steps;
Var priorStepPromise = steps%0%.exe c (this );

For (var I = 1, l = steps. length; I <l; I ++ ){
// Bind a step chain
PriorStepPromise = this. _ bind (priorStepPromise, steps [I]);
}
Return priorStepPromise;
}
}

Object. defineProperty (Sequence. prototype, 'steps ',{
Get: function (){
Var allSteps = this. engine. _ steps;
Return this. stepNames. map (function (stepName ){
Return allSteps [stepName];
})
}
});

Var engine = {
Retriable: function (name ){
This [name] = function (setTo ){
Var k = '_' + name;
If (arguments. length ){
This [k] = setTo;
Return this;
}
Return this [k];
}
Return this;
}
, Step: function (name ){
Var steps = this. _ steps
, Sequence = this. _ currSeq;

Sequence. stepNames. push (name );
This. _ currentStep =
Steps [name] | (steps [name] = new Step (name, this ));

This. retriable (name );
Return this;
}
, Accepts: function (input ){
This. _ currentStep. accepts = input & input. split (/\ s +/) | null;
Return this;
}
, Promises: function (output ){
This. _ currentStep. promises = output & output. split (/\ s +/) | null;
Return this;
}
, Do: function (name ){
This. _ currSeq =
This. _ stepSequences [name] | (this. _ stepSequences [name] = new Sequence (name, this ));
Return this;
}
, Promise: function (values ){
Return values? New Promise (values): new Promise ();
}
, _ StepSequences :{}
, _ Steps :{}

, Start: function (seqName ){
Var seq = this. _ stepSequences [seqName];
Seq. start ();
}
}

Engine
. Retriable ('errback ')
. Errback (function (err ){
Console. log ('errback', err );
});

Engine
. Do ('fetchhtml ')
. Step ('readurl ')
. Accepts ('')
. Promises ('url ')
. Step ('gethtml ')
. Accepts ('url ')
. Promises ('html ')
. Step ('savehtml ')
. Accepts ('url html ')
. Promises (null)

. ReadUrl (function (){
Var p = this. Promise ();
// Url.txt saves a URL
Fs. readFile ('./url.txt', 'utf8', function (err, data ){
If (err) p. fail (err );
Else p. fulfill (data );
});
Return p;
})

. GetHtml (function (url ){
Var p = this. Promise ();
Http. get (url, function (res ){
Var body = '';
Res. on ('data', function (c ){
Body + = c;
}). On ('end', function (){
P. fulfill (body );
});
}). On ('error', function (err ){
P. fail (err)
});
Return p;
})

. SaveHtml (function (url, html ){
Fs. writeFile ('./fetchresult', url + html, function (e ){
If (e) console. log ('error', e );
Else console. log ('done ');
});
})

. Start ('fetchhtml ')

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.