thunk関数とcoフレームを徹底的に理解する
19450 ワード
ES 6は多くの新しい特性を持ってきました.その中で、生成器、yieldなどは前のピラミッド式の非同期フィードバックに対してよく解決できます.このパッケージに基づくcoフレームは完全に同期した方式で非同期コードを作成することができます.この文章はジェネレータ関数とフレームthunkify、coのコアコードを比較的に徹底的に分析します.coはまだ広く使われています.私たちの日常的なコードのほかに、いくつかの有名なフレームもcoに基づいて実現されています.例えば、次世代のNodejs webフレームと呼ばれるkoaなどです.
ジェネレータ関数
ジェネレータ関数は、書き込みです.は、ジェネレータ関数を実行して一つのジェネレータに戻り、ジェネレータはthrow()の方法を持ち、手動で異常を投げてもよく、ジェネレータかどうかを判断するのに用いられます. は、ジェネレータ関数の内部でyield(またはyield*)を使用し、関数がyieldに実行されると実行を一時停止し、yieldの右値(関数コンテキスト、変数のバインディングなどの情報が保持されます)を返します.ジェネレータのnext()方法により、現在のyieldの右式の値(value属性)を含むオブジェクトを返します.およびgenerator関数が既に実行済みであるかどうかなどの情報.next()メソッドを実行するたびに、前回実行したyieldのところから下に降りて、次のyieldに会って、関連実行情報を含む対象に戻ってから一時停止して、次のnext()の実行を待つ. 生成器のnext()メソッドは、ieldの右式の値と実行済みの情報を含むオブジェクトを返します.next()メソッドのパラメータは前の一時停止でyieldの戻り値です. 以下は例で説明します.
例1:
ジェネレータ関数
ジェネレータ関数は、書き込みです.
function* func(){}
書式コードは、その本質も関数であるため、一般関数が持つすべての特性を備えています.これ以外にも、以下のような有用な特性がある.例1:
function test(){
return 'b';
}
function* func(){
var a = yield 'a';
consolone.log('gen:',a)/.gen:undefined var b=yield test()consolone.log('gen:',b)/.gen:undefined
}var func 1=func()var a=func 1.next();consolie.log('next:',a)/.next:{value:'a',done:false}var b=func 1.next();consolie.log('next:',b)/.next:{value:'b',done:false}var c=func 1.next();consolie.log('next:',c)//.next:{value:undefined,done:true} 3 :“ next() yield ; next() yield ”, next() , :var a = yield 'a'; a undefined。
:
2:
function test(){
return 'b';
}
function* func(){
var a = yield 'a';
console.log('gen:',a);// gen:1
var b = yield test();
console.log('gen:',b);// gen:2
}
var func2 = func();
var a = func2.next();
console.log('next:', a);// next: { value: 'a', done: false }
var b = func2.next(1);
console.log('next:', b);// next: { value: 'b', done: false }
var c = func2.next(2);
console.log('next:', c);// next: { value: undefined, done: true }
これは にはっきりしています.これ しません.
yieldについて*
yieldは を し、 のみを します. 、yield*は、 を のジェネレータまたは なオブジェクトに します. の を します
アーグメンントfunction* genFunc(){
yield arguments;
yield* arguments;
}
var gen = genFunc(1,2);
console.log(gen.next().value); // { '0': 1, '1': 2 }
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
Generatorfunction* gen1(){
yield 2;
yield 3;
}
function* gen2(){
yield 1;
yield* gen1();
yield 4;
}
var g2 = gen2();
console.log(g2.next().value); // 1
console.log(g2.next().value); // 2
console.log(g2.next().value); // 3
console.log(g2.next().value); // 4
thunk
coのアプリケーションでは、 コードを くように コードを くために、thunk を することが いです.ファイルの を み るステップ fs.readFile()のような で、thunk に する は の りです.function readFile(path, encoding){
return function(cb){
fs.readFile(path, encoding, cb);
};
}
では、thunk とは ですか?
thunk は の2つの を えています.があり、 つのパラメータだけがcalbackの です. calbackの のパラメータはerrorです. thunk を って、 にcoを して、 コードを くように、 コードを くことができます.var co = require('co'),
fs = require('fs'),
Promise = require('es6-promise').Promise;
function readFile(path, encoding){
return function(cb){
fs.readFile(path, encoding, cb);
};
}
co(function* (){// , co promise.then().then()..
var a = yield readFile('a.txt', {encoding: 'utf8'});
console.log(a); // a
var b = yield readFile('b.txt', {encoding: 'utf8'});
console.log(b); // b
var c = yield readFile('c.txt', {encoding: 'utf8'});
console.log(c); // c
return yield Promise.resolve(a+b+c);
}).then(function(val){
console.log(val); // abc
}).catch(function(error){
console.log(error);
});
かっこいいですか? にかっこいいです
は、 でthunk を くのは くさいです.thunkifyは に できるフレームがあります. のコードは の りです.var co = require('co'),
thunkify = require('thunkify'),
fs = require('fs'),
Promise = require('es6-promise').Promise;
var readFile = thunkify(fs.readFile);
co(function* (){// , co promise.then().then()..
var a = yield readFile('a.txt', {encoding: 'utf8'});
console.log(a); // a
var b = yield readFile('b.txt', {encoding: 'utf8'});
console.log(b); // b
var c = yield readFile('c.txt', {encoding: 'utf8'});
console.log(c); // c
return yield Promise.resolve(a+b+c);
}).then(function(val){
console.log(val); // abc
}).catch(function(error){
console.log(error);
});
thunkifyの について、 まかな は の りである./**
* Module dependencies.
*/
var assert = require('assert');
/**
* Expose `thunkify()`.
*/
module.exports = thunkify;
/**
* Wrap a regular callback `fn` as a thunk.
*
* @param {Function} fn
* @return {Function}
* @api public
*/
function thunkify(fn) {
assert('function' == typeof fn, 'function required');
// thunk , thunk yield, thunk
return function() {
var args = new Array(arguments.length);
// , fn
var ctx = this;
// ( , Array.prototype.slice.call(arguments) )
for (var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
// thunk ( callback , callback error)
// :
// function(cb) {fs.readFile(path, {encoding: 'utf8}, cb)}
return function(done) {
var called;
// , ; , push
args.push(function() {
if (called) return;
called = true;
done.apply(null, arguments);
});
try {
// ctx fn( , :fs.readFile)
// thunkify ( done ) , :
// fs.readFile(path, {encoding: 'utf8}, done)
// done , co
fn.apply(ctx, args);
} catch (err) {
done(err);
}
}
}
};
コードは ではないので、 を れば かります.
coフレーム
たちは のフレームを に べます./**
* slice() reference.
*/
var slice = Array.prototype.slice;
/**
* Expose `co`.
*/
module.exports = co['default'] = co.co = co;
/**
* Wrap the given generator `fn` into a
* function that returns a promise.
* This is a separate function so that
* every `co()` call doesn't create a new,
* unnecessary closure.
*
* @param {GeneratorFunction} fn
* @return {Function}
* @api public
*/
co.wrap = function(fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
/**
* Execute the generator function or a generator
* and return a promise.
*
* @param {Function} fn
* @return {Promise}
* @api public
*/
// gen ( ) (generator )
function co(gen) {
//
var ctx = this;
// gen
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
// Promise , co:
/**
* co(function*(){}).then(function(val){
*
* });
* */
return new Promise(function(resolve, reject) {
// gen
if (typeof gen === 'function') {
gen = gen.apply(ctx, args);
}
// gen ,
if (!gen || typeof gen.next !== 'function') {
return resolve(gen);
}
// , generator
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
// res : yield value ({done:false,value:''} value )
// ret : yield ( {done:false,value:''})
// generator : next() , yield ,
// next() yield (done) yield (value),
// next() :var a=yield cb();a ,
/**
* :co(function*(){
* var a = yield readFile('a.txt');
* console.log(a);
* var b = yield readFile('b.txt);
* console.log(b);
* });
* generator ,res a,b
* */
function onFulfilled(res) {
var ret;
try {
// co yield 。 co yield thunk ret.value thunk
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
// , :
/**
* co(function*(){
* return yield Promise.resolve(1);
* }).then(function(val){
* console.log(val); // 1
* });
* */
// ret.value then val
if (ret.done) {
return resolve(ret.value);
}
// ret.value Promise , :
// promise.then(onFulfilled).then(onFulfilled).then(onFulfilled)...
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) {
// onFulfilled yield 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) + '"'));
}
});
}
/**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
/**
* Convert a thunk to a promise.
*
* @param {Function}
* @return {Promise}
* @api private
*/
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function(resolve, reject) {
fn.call(ctx, function(err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}
/**
* Convert an array of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Array} obj
* @return {Promise}
* @api private
*/
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
/**
* Convert an object of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Object} obj
* @return {Promise}
* @api private
*/
function objectToPromise(obj) {
var results = new obj.constructor();
var keys = Object.keys(obj);
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
if (promise && isPromise(promise)) defer(promise, key);
else results[key] = obj[key];
}
return Promise.all(promises).then(function() {
return results;
});
function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
promises.push(promise.then(function(res) {
results[key] = res;
}));
}
}
/**
* Check if `obj` is a promise.
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
function isPromise(obj) {
return 'function' == typeof obj.then;
}
/**
* Check if `obj` is a generator.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
/**
* Check if `obj` is a generator function.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
/**
* Check for plain object.
*
* @param {Mixed} val
* @return {Boolean}
* @api private
*/
function isObject(val) {
return Object == val.constructor;
}
についてコメントします. 、 たちは の に づいて、coの プロセスを します.
たちの は:var co = require('co'),
thunkify = require('thunkify'),
fs = require('fs'),
Promise = require('es6-promise').Promise;
function readFile(path, encoding){
return function(cb){
fs.readFile(path, encoding, cb);
};
}
//var readFile = thunkify(fs.readFile);
co(function* (){// , co promise.then().then()..
var a = yield readFile('a.txt', {encoding: 'utf8'});
console.log(a); // a
var b = yield readFile('b.txt', {encoding: 'utf8'});
console.log(b); // b
var c = yield readFile('c.txt', {encoding: 'utf8'});
console.log(c); // c
return yield Promise.resolve(a+b+c);
}).then(function(val){
console.log(val); // abc
}).catch(function(error){
console.log(error);
});
まず、co() を します. は コンテキスト を しています.//
var ctx = this;
// gen
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
// Promise , co:
/**
* co(function*(){}).then(function(val){
*
* });
* */
return new Promise(function(resolve, reject) {
});
たちは にこのPromiseの を て をしましたか?if (typeof gen === 'function') {
gen = gen.apply(ctx, args);
}
は、まず、co() の のパラメータが かどうかを し、そうであれば、gen のパラメータをこの に して、genに る.ここではgenはジェネレータ ですので、ジェネレータを します.if (!gen || typeof gen.next !== 'function') {
return resolve(gen);
}
の で、もしgenがこの ジェネレータではないならば、 Promiseのresoveを して、 はgenをco().then(function(val){}に えます.のvalになりました
たちのこの はgenがジェネレータであり、 して します.onFulfilled();
その 、 たちはcoのコア :OnFulfilledに いました. たちはこの が をしたかを ます.function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
りを ぐために、 のエラーの はしばらく します.
この を めて します.レスポンス はundefinedです.そして、ジェネレータのnext()メソッドを します. の では します.var a = yield readFile('a.txt', {encoding: 'utf8'});
では、retは です.{
done: false,
value: function(cb){
fs.readFile(path, encoding, cb);
}
}
retをnext に えます.next は: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) + '"'));
}
まず、ジェネレータ で されたかどうかを し、 したら のresoveが されます. らかに の では のyieldまで しましたが、 が わっていません. が していない 、ret.valueをPromiseの に します.ここはthunk です.だから、toPromiseは に します.function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
/**
* Convert a thunk to a promise.
*
* @param {Function}
* @return {Promise}
* @api private
*/
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function(resolve, reject) {
fn.call(ctx, function(err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}
は、プロミスのインスタンスに ります.この で、fnも しました.fnは:function(cb){}で、ここに して、function{}がfnに えられたcbです. のパラメータはerrorオブジェクトです. つ のパラメータresはファイルを んだ のデータです.そして、resoveを して、 を のthen の に えます.ここで します.if (value && isPromise(value)) {
return value.then(onFulfilled, onRejected);
}
は、つまりオンフルFilledのパラメータresである. の の によれば、resはジェネレータのnext()メソッドに えられたものであり、つまり、co ジェネレータの パラメータのvar=yield readFile('a.txt',encoding:'utf 8');のaの は と た になることができます.
このように、thunk に づくcoフレーム のプログラミングは、 のPromise、Generator、Generator Function、Object、Arayモードの があり、これ の は われない.
coの ロジックを して、 い をよりよく できます. はkoaなどのcoベースの みを うことによって、 たちももっと く になります.
coの は、coの ロジックをより く するために、インターネット でもう つの の があります.function co(generator) {
return function(fn) {
var gen = generator();
function next(err, result) {
if(err){
return fn(err);
}
var step = gen.next(result);
if (!step.done) {
step.value(next);
} else {
fn(null, step.value);
}
}
next();
}
}
しかし、この は、yieldの ろだけがthunk である をサポートします. :var co = require('./co');
// wrap the function to thunk
function readFile(filename) {// ,yield thunk
return function(callback) {
require('fs').readFile(filename, 'utf8', callback);
};
}
co(function * () {
var file1 = yield readFile('./file/a.txt');
var file2 = yield readFile('./file/b.txt');
console.log(file1);
console.log(file2);
return 'done';
})(function(err, result) {
console.log(result)
});
されますcontent in a.txt
content in b.txt
done