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
, 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

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
The commands are exactly the same.

Encapsulation of asynchronous tasks


Function will operate asynchronously
Expressed very concisely
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 = '' ; var result = yield fetch(url); console .log(; } var g = gen(); var result =; result.value.then( function ( data ) { return data.json(); }).then( function ( data ) {; }); Copy code

Execute first

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

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

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

The language is called by value, its
Function meaning is different

2. In
What the function replaces is not an expression, but
Multi-parameter function
, Replace it with a
Only accept callback functions as parameters
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 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 this , .. .args, callback); //or //args.push(callback) //return fn.apply(this, args); } }; }; var readFileThunk = Thunk(fs.readFile); readFileThunk(fileA)(callback); Copy code
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 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 =; //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 =; while (!res.done){ console .log(res.value); res =; } Copy code

In the above code,

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 =; //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 =; r2.value( function ( err, data ) { if (err) throw err;; }); }); Copy code

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

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

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

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 =; 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 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 =; if (result.done) return ; result.value(next); } next(); } run(g); Copy code

Of the above code

Function is just a
Automatic executor of Generator function


The function is
Callback function.

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

co module

A small tool for

Automatic execution of functions.

Below is a

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

Module allows you
No need to write generator function executor

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

In the above code,

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

The function returns a
Object, so you can use
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

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
Function, return the right of execution in the callback function

Object. Wrap the asynchronous operation into
Object, use
Method to return the right to execute

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

Prerequisites for using co
Yes, the generator function
behind the yield command
, Only
Is the Thunk function
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(); function ( data ) { function ( data ) {; }); }); Copy code

3. Write an automatic actuator based on manual execution.

function run ( gen ) { var g = gen(); function next ( data ) { var result =; //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

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
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


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

The above code allows three concurrent

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

Processing Stream

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

Mode use
EventEmitter API
, 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.


Function, you can determine which of these three events occurred first, only when
When the event occurs first, the processing of the next data block is entered. Thus, we can pass a
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

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