The basic concept
Coroutines: Multiple threads collaborate to complete asynchronous tasks
Coroutines are a little bit like functions and a little bit like threads. Its operation process is roughly as follows:
Step one, coroutine A starts executing.
In the second step, coroutine A is paused halfway through execution, and execution authority is transferred to coroutine B.
In the third step, the coroutine B returns execution authority.
Step 4, coroutine A resumes execution.
Coroutine of the process aboveA
Is an asynchronous task because itExecute in two (or more) segments
function* asyncJob() {
/ /... Other code
var f = yield readFile(fileA);
/ /... Other code
}
Copy the code
The coroutine pauses at yield, returns to execution, and continues from where it was suspended. The biggest advantage of this is that the code is written very much like a synchronous operation, which is exactly the same without the yield command.
Encapsulation of asynchronous tasks
While Generator functions represent asynchronous operations succinctly, process management (i.e. when to perform phase one and when to perform phase two) is inconvenient.
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 the code
The Generator function is executed to retrieve the traverser object, and the next method (line 2) is used to perform the first phase of the asynchronous task. Since the Fetch module returns a Promise object, the next next method is called using the then method.
The Thunk function is a way of automatically executing Generator functions
“Call by Value”
That is, before entering the body of the function, we compute the value of x + 5 (equal to 6) and pass that value into f. C uses this strategy.
var x = 1;
function f(m) {
return m * 2;
}
f(x + 5)
// When called, is equal to
f(6)
Copy the code
“Call by Name”
The expression x + 5 is passed directly into the function body and evaluated only when it is used. The Haskell language uses this strategy.
var x = 1;
function f(m) {
return m * 2;
}
f(x + 5)
// When the name is called, it is equivalent to
(x + 5) * 2
Copy the code
The meaning of the Thunk function, an implementation strategy for “calling by name”
The compiler’s “call by name” implementation usually puts arguments in a temporary function that is passed into the function body. This temporary function is called the Thunk function.
The Thunk function is an implementation strategy for “calling by name” to replace an expression.
function f(m) {
return m * 2;
}
f(x + 5);
/ / is equivalent to
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
Copy the code
Thunk function of the JavaScript language
1.JavaScript
The language is pass-value call, itsThunk
Functions have different meanings
2, inJavaScript
In the language,Thunk
Instead of replacing an expression, the function replacesMultiparameter function
, and replace it with oneOnly callback functions are accepted as arguments
theOne parameter function
3, JS Thunk: equivalent to a function that needs to pass multiple arguments (one of which is CB), divided into three executions
The first time Thunk receives the function to be executed (fn) returns a function
The second execution passes parameters other than cb (… The args)
The third execution only passes cb
// Normal version of readFile (multi-parameter version)
fs.readFile(fileName, callback);
// Thunk readFile (single-argument version)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
// Calling Thunk returns a function that accepts only cb
var readFileThunk = Thunk(fileName);
// Just pass in cb
readFileThunk(callback);
Copy the code
Trunk function encapsulation
Any function whose argument has a callback function can be written as 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) {
// Since aegs is an array, we need to destruct the assignment 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 the code
The sample
const Thunk = function(fn) {
return function (. args) {
return function (callback) {
// Since aegs is an array, we need to destruct the assignment 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-destructible scope
// ft is the first function to return
// 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) returns the second function that only accepts cb
// function (callback){
// args.push(callback);
// return fn.apply(this, args);
// }
/ / ft (1) is equivalent to (the console. The log)
f(1.console.log)
Copy the code
Thunkify module
For production converters, the Thunkify module is recommended.
The first is installation.
$ npm install thunkify
Copy the code
The usage is as follows (to call twice in a row and pass in the required parameters, 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 toFs.readfile (fileName, cb)Copy the code
Thunkify’s source code mainly adds a checking mechanism, the variable called to ensure that the callback function is run only 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 The CB is executed only once
Copy the code
Process management of Generator functions
function* gen() {
// ...
}
var g = gen();
var res = g.next();
while(! res.done){console.log(res.value);
res = g.next();
}
Copy the code
In the code above, the Generator function Gen performs all the steps automatically, but it is not suitable for asynchronous operations. There is no guarantee that the previous step will be completed before the next step can be executed.
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
// Each yield of a 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
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 the code
A close look at the code above shows that the Generator function executes by passing the same callback function repeatedly to the value property of the next method. This allows us to automate the process with recursion.
Automatic process management of the Thunk function
The real power of the Thunk function is that it automatically executes Generator functions
With this actuator, it is much easier to execute Generator functions. Pass the Generator function to the run function, no matter how many asynchronous operations are inside.
The premise is that every asynchronous operation must be a Thunk function, which means that the yield command must be followed by a Thunk function
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 the code
Complete sample
/ / 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 Generator functions
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 the code
The run function of the above code is an automatic executor of a Generator function.
The internal next function is the Thunk callback.
The next function moves the pointer to the next step in the Generator function (the Gen. next method) and then determines whether the Generator function is finished (the result.done property). If not, Pass next to Thunk (result.value), or exit.
Co module
A small utility for automatic execution of Generator functions.
Below is a Generator function that reads two files in turn.
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
Copy the code
The CO module saves you from writing actuators for Generator functions.
var co = require('co');
co(gen);
Copy the code
In the code above, the Generator function is automatically executed as soon as the CO function is passed in.
The CO function returns a Promise object, so callbacks can be added using the then method.
co(gen).then(function (){
console.log('Generator function completed execution ');
});
Copy the code
Asynchronously completes the implementation of the rollback execution authority
A Generator is a container for asynchronous operations. Its automatic execution requires a mechanism that automatically cedes execution rights when an asynchronous operation has a result. There are two ways to do this.
(1) Callback function. Wrap asynchronous operations asThunk
Function, in the callback function to return the execution
(2)Promise
Object. Wrap asynchronous operations asPromise
Object, withthen
Method return execution authority
The CO module is essentially a module that wraps two kinds of autoactuators (the Thunk function and the Promise object) into one.
The prerequisite for using CO is that the yield of a Generator function must be followed by either a Thunk function or a Promise object.
Automatic execution based on Promise objects
1. First, wrap the FS module’s readFile method as 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 the code
2. Then manually execute the Generator function above
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});
Copy the code
3, according to the manual execution, write an automatic executor.
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
// The last step to check whether the current function is Generator, if so return.
if (result.done) return result.value;
// Use the then method to add the callback function to the return value, and then call the next function again.
result.value.then(function(data){
next(data);
});
}
next();
}
run(gen);
Copy the code
Handles concurrent asynchronous operations
Co supports concurrent asynchronous operations that allow certain operations to take place at the same time and wait until they are all complete before proceeding to the next step.
In this case, all concurrent operations are placed in arrays or objects, following the yield statement.
// Array
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)];console.log(res);
}).catch(onerror);
// How to write objects
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),};console.log(res);
}).catch(onerror);
Copy the code
The sample
// Use an array loop
co(function* () {
var values = [n1, n2, n3];
yield values.map(somethingAsync);
});
function* somethingAsync(x) {
// do something async
return y
}
Copy the code
The code above allows three somethingAsync asynchronous operations to be concurrent and wait until they are all complete before proceeding to the next step.
To deal with the Stream
Node provides Stream mode for reading and writing data, which processes only one piece of data at a time. This is great for working with large numbers of data.
Stream mode uses the EventEmitter API, which releases three events:
- Data event: The next block of data is ready.
- End event: The entire “data stream” is finished.
- Error event: An error occurs.
Using the promise.race () function, you can determine which of these three events occurs first, and only when the data event occurs first will the next block of data be processed. Thus, we can read all the data through a while loop.
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 the code
The above code reads the Les Miserables text file in Stream mode, uses the stream.once method for each data block, and adds one-time callbacks to the data, end, and error events. The variable res has a value only when a data event occurs, and then accumulates the number of occurrences of the word valjean in each data block.