Asynchronous application of ES6(13)Generator

Asynchronous application of ES6(13)Generator

basic concepts

Coroutine: multiple threads cooperate with each other to complete asynchronous tasks

A coroutine is a bit like a function, but also a bit like a thread. Its running process is roughly as follows:

In the first step, the coroutine A starts to execute.

In the second step, the execution of coroutine A is halfway through, enters a pause, and the execution right is transferred to coroutine B.

In the third step, (after a period of time) Coroutine B returns the execution right.

In the fourth step, the coroutine A resumes execution.

The coroutine of the above process
A
, Which is an asynchronous task, because it
Divide into two (or more) executions

function * asyncJob () { //... other code var f = yield readFile(fileA); //... other code } Copy code

Coroutine encounter

yield
The order will be suspended, and the execution will be resumed from the place where it was suspended. Its biggest advantage is that the code is written very much like a synchronous operation, if you remove
yield
The commands are exactly the same.

Encapsulation of asynchronous tasks

although

Generator
Function will operate asynchronously
Expressed very concisely
,but
Process management is inconvenient
(That is, when to execute the first phase and when to execute the second phase).

var fetch = require ( 'node-fetch' ); function * gen () { var url = 'https://api.github.com/users/github' ; var result = yield fetch(url); console .log(result.bio); } var g = gen(); var result = g.next(); result.value.then( function ( data ) { return data.json(); }).then( function ( data ) { g.next(data); }); Copy code

Execute first

Generator
Function, get the iterator object, and then use
next
Method (second line), execute the first stage of the asynchronous task. due to
Fetch
What the module returns is a
Promise
Object, so use
then
Method call next
next
method.

The Thunk function is a way to automatically execute the Generator function

"Call by value"

That is, before entering the function body, calculate the value of x + 5 (equal to 6), and then pass this value into the function f. The C language uses this strategy.

var x = 1 ; function f ( m ) { return m * 2 ; } F (X + . 5 ) //a call-by time, equivalent to F ( . 6 ) Copy the code

"Call by name"

That is, directly pass the expression x + 5 into the function body, and evaluate it only when it is used. The Haskell language uses this strategy.

var x = 1 ; function f ( m ) { return m * 2 ; } F (X + . 5 ) //when invoked by name, is equivalent to (X + . 5 ) * 2 duplicated code

The meaning of Thunk function, an implementation strategy of "call by name"

The "call by name" implementation of the compiler is often

Put the parameters in a temporary function
, And then pass this temporary function into the function body. This temporary function is called
Thunk
function.

Thunk
Function is an implementation strategy of "call by name", which is used to replace a certain expression.

function f ( m ) { return m * 2 ; } f(x + 5 ); //Equivalent to var thunk = function () { return x + 5 ; }; function f ( thunk ) { return thunk() * 2 ; } Copy code

Thunk function of JavaScript language

1,
JavaScript
The language is called by value, its
Thunk
Function meaning is different

2. In
JavaScript
Language,
Thunk
What the function replaces is not an expression, but
Multi-parameter function
, Replace it with a
Only accept callback functions as parameters
of
Single parameter function

3. Thunk of js: It is equivalent to dividing the function that needs to pass multiple parameters (one of which is cb) into three executions

The first execution of Thunk receives the function to be executed (fn) and returns a function
The second execution passes parameters other than cb (...args)
The third execution only passes cb
//The normal version of readFile (multi-parameter version) fs.readFile(fileName, callback); //Thunk version of readFile (single parameter version) var Thunk = function ( fileName ) { return function ( callback ) { return fs.readFile(fileName, callback); }; }; //Call Thunk to return a function that only accepts cb var readFileThunk = Thunk(fileName); //Recall only need to pass in cb readFileThunk(callback); Copy code

Trunk function encapsulation

Any function, as long as the parameter has a callback function, can be written in the form of a Thunk function
//ES5 version var Thunk = function ( fn ) { return function () { var args = Array .prototype.slice.call( arguments ); return function ( callback ) { args.push(callback); return fn.apply( this , args); } }; }; //ES6 version const Thunk = function ( fn ) { return function ( ...args ) { return function ( callback ) { //Because aegs is an array, it needs to be destructured and assigned when calling return fn.call( this , .. .args, callback); //or //args.push(callback) //return fn.apply(this, args); } }; }; var readFileThunk = Thunk(fs.readFile); readFileThunk(fileA)(callback); Copy code
Example
const Thunk = function ( fn ) { return function ( ...args ) { return function ( callback ) { //Because aegs is an array, it needs to be destructured and assigned when calling return fn.call( this , ...args, callback ); //or //args.push(callback) //return fn.apply(this, args); } }; }; function f ( a, cb ) { cb(a); } const ft = Thunk(f); //Form a non-destroyed scope //ft is the first return function //function (){ //var args = Array.prototype.slice.call(arguments); //console.log(args); //return function (callback){ //args.push(callback); //return fn.apply(this, args); //} //}; ft( 1 )( console .log) //1 //ft(1) gets the second return function that only accepts cb //function (callback){ //args.push(callback); //return fn. apply(this, args); //} //ft(1)(console.log) is equivalent to f( 1 , console .log) Copy code

Thunkify module

For converters in the production environment, it is recommended to use the Thunkify module.

The first is installation.

$ Npm install thunkify copy the code

The usage is as follows (

To call twice in a row, pass in the required parameters in turn, to ensure that only one is passed in at the end
)

var thunkify = require ( 'thunkify' ); var fs = require ( 'fs' ); var read = thunkify(fs.readFile); read( 'package.json' )( function ( err, str ) { //... }); //Equivalent to fs.readFile(fileName, cb) Copy code

The source code of Thunkify mainly has a check mechanism, the variable called ensures that the callback function is only run once.

var thunkify = require ( 'thunkify' ); function f ( a, b, callback ) { var sum = a + b; callback(sum); callback(sum); } var ft = thunkify(f); var print = console .log.bind( console ); ft( 1 , 2 )(print); //3 cb only executes the copy code once

Generator function process management

function * gen () { //... } var g = gen(); var res = g.next(); while (!res.done){ console .log(res.value); res = g.next(); } Copy code

In the above code,

Generator
function
gen
Will automatically complete all steps, but
Not suitable for asynchronous operation
, There is no guarantee that the previous step can be executed before the next step can be executed.

var fs = require ( 'fs' ); var thunkify = require ( 'thunkify' ); var readFileThunk = thunkify(fs.readFile); //Each yield of the Generator function must be an asynchronous Thunk function var gen = function * () { var r1 = yield readFileThunk( '/etc/fstab' ); console .log(r1.toString()); var r2 = yield readFileThunk( '/etc/shells' ); console .log(r2.toString() ); }; var g = gen(); var r1 = g.next(); //r1={value:[Function],done:false} //Call value to pass in the callback function to execute the callback function r1.value( function ( err, data ) { if (err) throw err; var r2 = g.next(data); r2.value( function ( err, data ) { if (err) throw err; g.next(data); }); }); Copy code

If you look carefully at the code above, you can find

Generator
The execution process of the function is actually passing the same callback function repeatedly
The value attribute of the next method
. This allows us to use recursion to automate this process.

Automatic process management of Thunk function

The real power of the Thunk function is that it can

Automatic execution of Generator function

With this actuator, execute

Generator
The function is much more convenient. Regardless of how many asynchronous operations there are, directly
Generator
Function passed in
run
Function.

The premise is that every asynchronous operation must be a Thunk function, that is, the Thunk function that follows the yield command must be

function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } function* g() { //... } run(g); Copy code

Complete example

//Thunk function var Thunk = function ( fn ) { return function () { var args = Array .prototype.slice.call( arguments ); return function ( callback ) { args.push(callback); return fn.apply( this , args); } }; }; //Call Thunk to generate the function after the first return var readFileThunk = Thunk(fs.readFile); //Function g encapsulates n asynchronous file reading operations var g = function * () { var f1 = yield readFileThunk( 'fileA' ); var f2 = yield readFileThunk( 'fileB' ); //... var fn = yield readFileThunk( 'fileN' ); }; //Automatically execute the Generator function function run ( fn ) { //Call the Generator function var gen = fn(); function next ( err, data ) { var result = gen.next(data); if (result.done) return ; result.value(next); } next(); } run(g); Copy code

Of the above code

run
Function is just a
Automatic executor of Generator function
.

Internal

next
The function is
Thunk
Callback function.

next
The function first moves the pointer to
Generator
The next step of the function (
gen.next
Method), and then judge
Generator
Whether the function ends (
result.done
Attribute), if it is not over, it will
next
Function and then pass in
Thunk
function(
result.value
Attribute), otherwise just exit directly.

co module

A small tool for

Generator
Automatic execution of functions.

Below is a

Generator
Function, used to read two files in sequence.

var gen = function * () { var f1 = yield readFile( '/etc/fstab' ); var f2 = yield readFile( '/etc/shells' ); console .log(f1.toString()); console .log(f2.toString() ); }; Copy code

co
Module allows you
No need to write generator function executor
.

var co = require ( 'co' ); co(gen); Copy code

In the above code,

Generator
Function just pass in
co
Function, it will be executed automatically.

co
The function returns a
Promise
Object, so you can use
then
Method to add a callback function.

co(gen).then( function () { console .log( 'Generator function execution completed' ); }); Copy code

Asynchronous completion of the realization of the return of execution rights

Generator
It is a container for asynchronous operations. Its automatic execution requires a mechanism, when the asynchronous operation has a result, the execution right can be automatically returned. There are two ways to do this.

(1) Callback function. Wrap the asynchronous operation into
Thunk
Function, return the right of execution in the callback function

(2)
Promise
Object. Wrap the asynchronous operation into
Promise
Object, use
then
Method to return the right to execute

co
The module actually combines two automatic actuators (
Thunk
Function and
Promise
Object), packaged into a module.

Prerequisites for using co
Yes, the generator function
behind the yield command
, Only
Is the Thunk function
or
Promise object
.

Automatic execution based on Promise objects

1. 1. wrap the readFile method of the fs module into a Promise object

var fs = require ( 'fs' ); var readFile = function ( fileName ) { return new Promise ( function ( resolve, reject ) { fs.readFile(fileName, function ( error, data ) { if (error) return reject(error); resolve(data); }); }); }; var gen = function * () { var f1 = yield readFile( '/etc/fstab' ); var f2 = yield readFile( '/etc/shells' ); console .log(f1.toString()); console .log(f2.toString() ); }; Copy code

2. Then, manually execute the above Generator function

var g = gen(); g.next().value.then( function ( data ) { g.next(data).value.then( function ( data ) { g.next(data); }); }); Copy code

3. Write an automatic actuator based on manual execution.

function run ( gen ) { var g = gen(); function next ( data ) { var result = g.next(data); //Check whether the current step is the last step of the Generator function, and return if it is. if (result.done) return result.value; //Use the then method to add a callback function to the return value, and then call the next function again. result.value.then( function ( data ) { next(data); }); } next(); } run(gen); Copy code

Handling concurrent asynchronous operations

co
stand by
Concurrent asynchronous operation
, Which allows certain operations to be performed at the same time, and waits until they are all completed before proceeding to the next step.

At this time, to

Concurrent operations
Put all on
Array or object
,follow
yield
After the statement.

//How to write an array co( function * () { var res = yield [ Promise .resolve( 1 ), Promise .resolve( 2 ) ]; console .log(res); }).catch(onerror); //The writing of the object co( function * () { var res = yield { 1 : Promise .resolve( 1 ), 2 : Promise .resolve( 2 ), }; console .log(res); }).catch(onerror); Copy code

Example

//Using array loop co( function * () { var values = [n1, n2, n3]; yield values.map(somethingAsync); }); function * somethingAsync ( x ) { //do something async return y } Copy code

The above code allows three concurrent

somethingAsync
Asynchronous operations, wait until they are all completed before proceeding to the next step.

Processing Stream

Node
provide
Stream
Mode reads and writes data, the characteristics are
once
Data-only
Part of
, Data is divided into
Block by block
,just like
"data flow"
same. This for
Processing large-scale data
Very advantageous.

Stream
Mode use
EventEmitter API
, 3.events will be released:

  • data event: The next data block is ready.
  • End event: The entire "data stream" has been processed.
  • error event: An error occurred.

use

Promise.race()
Function, you can determine which of these three events occurred first, only when
data
When the event occurs first, the processing of the next data block is entered. Thus, we can pass a
while
Loop to complete the reading of all data.

const co = require ( 'co' ); const fs = require ( 'fs' ); const stream = fs.createReadStream( './les_miserables.txt' ); let valjeanCount = 0 ; co( function *() { while ( true ) { const res = yield Promise .race([ new Promise ( resolve => stream.once( 'data' , resolve)), new Promise ( resolve => stream.once( 'end' , resolve) ), new Promise ( ( resolve, reject ) => stream.once( 'error', reject)) ]); if (!res) { break ; } stream.removeAllListeners( 'data' ); stream.removeAllListeners( 'end' ); stream.removeAllListeners( 'error' ); valjeanCount += (res.toString().match( /valjean/ig ) || []).length; } console .log( 'count:' , valjeanCount); //count: 1120 }); Copy code

The above code uses

Stream
The mode reads the text file of "Les Miserables" and uses it for each data block
stream.once
Method in
data, end, error
Add a one-time callback function to the three events. variable
res
only at
data
The value is only available when the event occurs, and then accumulated in each data block
valjean
The number of occurrences of the word.