The meaning and usage of co function library
Before entering the text, insert a message first.
The “Software Caprice” I translated seven years ago was republished ( JD link ). This time, the two volumes of “Joel on Software” are republished at the same time. The first volume is a new translation, and the second volume is my translation.
The author of this book is Joel Splosky , a well-known programmer and founder of StackOverflow . I think it is one of the best readings for software project management, and it is recommended to read.
=======================================
The following is the third article in the series of “In-depth mastery of ECMAScript 6 asynchronous programming”.
- The meaning and usage of Generator function
- The meaning and usage of Thunk function
- The meaning and usage of co function library
- The meaning and usage of async function
1. What is the co library?
The co function library is a small tool released by the famous programmer TJ Holowaychuk in June 2013 for the automatic execution of Generator functions.
For example, there is a Generator function for reading 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()); };
The co function library allows you to not write the executor of the Generator function.
var co = require('co'); co(gen);
In the above code, the Generator function will be automatically executed as long as the co function is passed in.
The co function returns a Promise object, so you can use the then method to add a callback function.
co(gen).then(function (){ console.log('Generator 函数执行完成'); })
In the above code, when the Generator function is finished, a line of prompt will be output.
Second, the principle of co function library
Why can co automatically execute the Generator function?
As mentioned in the previous article, the Generator function 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 a Thunk function, and return the execution right in the callback function.
(2) Promise object. Wrap the asynchronous operation into a Promise object, and use the then method to return the right of execution.
The co function library actually packs two automatic executors (Thunk function and Promise object) into one library. The prerequisite for using co is that after the yield command of the Generator function, there can only be a Thunk function or a Promise object.
The previous article has introduced the automatic executor based on the Thunk function. Let’s look at the automatic executor based on the Promise object. This is necessary to understand the co library.
Three, automatic execution based on Promise objects
Still use the above example. First, 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) 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()); };
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); }); })
Manual execution is actually using the then method, adding callback functions layer by layer. Understand this, you can write an automatic actuator.
function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); } run(gen);
In the above code, as long as the Generator function has not been executed to the last step, the next function calls itself to achieve automatic execution.
Fourth, the source code of the co function library
co is an extension of the auto-executor above. Its source code is only dozens of lines, which is very simple.
First, the co function accepts the Generator function as a parameter and returns a Promise object.
function co(gen) { var ctx = this; return new Promise(function(resolve, reject) { }); }
In the returned Promise object, co first checks whether the parameter gen is a Generator function. If it is, execute the function and get an internal pointer object; if it is not, return and change the state of the Promise object to resolved.
function co(gen) { var ctx = this; return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.call(ctx); if (!gen || typeof gen.next !== 'function') return resolve(gen); }); }
Next, co wraps the next method of the internal pointer object of the Generator function into an onFulefilled function. This is mainly to be able to catch the thrown error.
function co(gen) { var ctx = this; return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.call(ctx); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } }); }
Finally, there is the key next function, which calls itself repeatedly.
function next(ret) { if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } });
In the above code, the internal code of the next function has only four lines of commands in total.
In the first line, check whether it is the last step of the Generator function, and return if it is.
The second line ensures that the return value of each step is a Promise object.
In the third line, use the then method to add a callback function to the return value, and then call the next function again through the onFulfilled function.
In the fourth line, when the parameters do not meet the requirements (parameters are not Thunk functions and Promise objects), the state of the Promise object is changed to rejected, thereby terminating the execution.
Five, concurrent asynchronous operation
co supports concurrent asynchronous operations, that is, allowing certain operations to be performed at the same time, and wait until they are all completed before proceeding to the next step.
At this time, all concurrent operations must be placed in an array or object.
// 数组的写法 co(function* () { var res = yield [ Promise.resolve(1), Promise.resolve(2) ]; console.log(res); }).catch(onerror); // 对象的写法 co(function* () { var res = yield { 1: Promise.resolve(1), 2: Promise.resolve(2), }; console.log(res); }).catch(onerror);
(over)