首先這篇文章是參考阮一峰老師的ES6章節(jié)中的generator部分寫的,再加上我自己的理解,新手如果要學(xué)習(xí)ES6推薦去看阮老師的 ES6
異步
所謂的異步就是假如說現(xiàn)在有倆個過程,第一個過程為我正在做飯,第二個過程為在我做飯的期間有一個人來敲門,這種情形對于異步來說就是,我首先需要停止做飯,然后去開門,當(dāng)人進(jìn)來之后,我把門關(guān)上了,然后繼續(xù)做飯去了,簡單的說就是我停止一個過程,去做另一個過程,然后另一個過程結(jié)束后再來繼續(xù)做原來沒有做完的過程
異步處理的幾種方式
回調(diào)函數(shù)的異步
我們以讀取文件為例
fs.readFile(fileA, 'utf-8', function(err, data){
fs.readFile(fiileB, 'utf-8', function(err,data)}{)
})
readFile()函數(shù)是nodejs中的,首先讀取fileA,然后讀取fileA結(jié)束后調(diào)用回調(diào)函數(shù)然后在讀取fileB,上面如果讀取多個文件會看起來特別冗余,那么ES6中Promise就起到了作用
Promise函數(shù)的異步處理
還是以上述讀取文件的例子
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data) {
console.log(data);
}).then(function(){
return readFile(fileB);
}).then(function(data){
console.log(data);
}).catch(function(err){
console.log(err);
})
這個的意思就是引用fs-readfile-promise模板,使用bash的可以用npm install --save-dev fs-readfile-promise來安裝,這個模塊返回一個promise版本的readFile()所以它可以調(diào)用then方法,然后在打印出fileA中的數(shù)據(jù),then返回一個promise然后在調(diào)用then方法,然后返回promise版本的readFile,返回具有readFile的promise,然后在調(diào)用then輸出fileB的數(shù)據(jù)
generator實現(xiàn)異步
var fetch = require('node-fech');
function *gen(){
var url = 'haha';
var result = yield fetch(url);
console.log(url);
}
var g = new gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
首先先創(chuàng)建了一個generator函數(shù),然后fetch函數(shù)返回的是一個promise對象,所以首先第一次調(diào)用next(),返會的是一個對象,這個對象的value屬性就是fetch返回的promise對象,然后調(diào)用then函數(shù)將數(shù)據(jù)轉(zhuǎn)為json,然后then函數(shù)返回一個promise對象,然后在調(diào)用then對象,然后把上一個then返回的數(shù)據(jù)傳遞給第二個then里面的function函數(shù)
thunk函數(shù)
什么是thunk函數(shù)
簡單的說就是如果你想往一個函數(shù)中傳遞一個x+5,參數(shù),現(xiàn)在就把這個x+5用一個函數(shù)表示,如下
var thunk = function(){
return x+5
}
這個用來表示x+5的函數(shù)就是thunk
js中的thunk函數(shù)
fs.readFile(fileName, callback);
var thunk = function(fileName) {
return function(callback){
return fs.readFile(filename, callback);
}
}
var read = thunk(fileName)(callback);
簡單的thunk函數(shù)轉(zhuǎn)換器
var thunk = function(fn){
return function(){
var args = Array.prototype.slice.call(arguments);
return function(callback){
args.push(callback);
return fn.apply(this, args);
}
}
}
分析上面Array.prototype.slice.call(arguments);這一行代碼,就是slice函數(shù)是在原型對象中,然后使slice被arguments調(diào)用,因為slice中沒有其他參數(shù),所以這行代碼直接返回一個數(shù)組,這個數(shù)組其實就是arguments中各參數(shù)組成的
thunkify模塊
安裝該模塊使用 npm install thunkify
var thunkify = require('thunkify');
var fs = require('fs');
read('package.json')(function(err, str){})
下面我們來分析一下thunkify的源碼
function thunkify(fn){
return function(){
//第一個返回函數(shù)就是把fn的參數(shù)傳遞給args這個數(shù)組
var args = new Array(arguments.length);
//ctx就是上下文環(huán)境
var ctx = this;
for(var i = 0; i < args.length; ++i){
args[i] = arguments[i];
}
//第二個返回的函數(shù),這里的done相當(dāng)于回調(diào)函數(shù)這里保證回調(diào)函數(shù)只
//被執(zhí)行一次
return function(done){
var called;
args.push(function(){
if(called) return;
called = true;
done.apply(null, arguments);
})
//執(zhí)行fn函數(shù)然后,這里邊的args包括fn函數(shù)以及保證回調(diào)函數(shù)執(zhí)行一次
try{
fn.apply(ctx, args);
}catch(err){
done(err);
}
}
}
}
function f(a, c, callback){
var sum = a + b;
callback(sum);
callback(sum);
}
var ft = thunkify(f); //第一次調(diào)用thunkify函數(shù)就是返回第一個函數(shù)
var print = console.log.bind(console);
ft(1, 2)(print); //ft(1,2),這時候把1,2加入args,然后這里面的print就相于
//done,也就是callback,所以callback只會被調(diào)用一次
自動執(zhí)行g(shù)enerator函數(shù)
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
//下面是怎樣實現(xiàn)generator函數(shù)自動執(zhí)行
var g = gen();
var r1 = g.next();
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);
});
});
從以上可以看出主要是通過next函數(shù)實現(xiàn)generator函數(shù)執(zhí)行的,往next中傳入的值,也就是yield返回的值,也就是gen函數(shù)中的r1和r2
thunk實現(xiàn)generator函數(shù)自動執(zhí)行
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};
run(g);
這里面的在yield后面的函數(shù)就是thunk函數(shù),上面的next函數(shù)就是thunk函數(shù)的回調(diào)函數(shù),也就是執(zhí)行的函數(shù),所以上面的generator能夠一直自動執(zhí)行
CO模板實現(xiàn)generator函數(shù)自動執(zhí)行
var co = require('co');
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co(gen);
其實你會發(fā)現(xiàn)這個co模塊和上面的run函數(shù)的功能一樣,所以這個co模塊和run函數(shù)的實現(xiàn)都差不多
promise來實現(xiàn)generator自動執(zhí)行
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());
};
上面吧readFile函數(shù)包裝成一個promise對象,以實現(xiàn)在動執(zhí)行
CO源碼分析
function co(gen) {
var ctx = this;
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)
+ '"'
)
);
}
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);
}
});
}
上面代碼中,next函數(shù)的內(nèi)部代碼,一共只有四行命令。
第一行,檢查當(dāng)前是否為 Generator 函數(shù)的最后一步,如果是就返回。
第二行,確保每一步的返回值,是 Promise 對象。
第三行,使用then方法,為返回值加上回調(diào)函數(shù),然后通過onFulfilled函數(shù)再次調(diào)用next函數(shù)。
第四行,在參數(shù)不符合要求的情況下(參數(shù)非 Thunk 函數(shù)和 Promise 對象),將 Promise 對象的狀態(tài)改為rejected,從而終止執(zhí)行。
有時間繼續(xù)分析這個源碼今天太累了