這篇文章將為大家詳細講解有關如何在ES6中自動執(zhí)行Generator,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

單個異步任務
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}為了獲得最終的執(zhí)行結果,你需要這樣做:
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});首先執(zhí)行 Generator 函數(shù),獲取遍歷器對象。
然后使用 next 方法,執(zhí)行異步任務的第一階段,即 fetch(url)。
注意,由于 fetch(url) 會返回一個 Promise 對象,所以 result 的值為:
{ value: Promise { <pending> }, done: false }最后我們?yōu)檫@個 Promise 對象添加一個 then 方法,先將其返回的數(shù)據(jù)格式化(data.json()),再調(diào)用 g.next,將獲得的數(shù)據(jù)傳進去,由此可以執(zhí)行異步任務的第二階段,代碼執(zhí)行完畢。
多個異步任務
上節(jié)我們只調(diào)用了一個接口,那如果我們調(diào)用了多個接口,使用了多個 yield,我們豈不是要在 then 函數(shù)中不斷的嵌套下去……
所以我們來看看執(zhí)行多個異步任務的情況:
var fetch = require('node-fetch');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var r2 = yield fetch('https://api.github.com/users/github/followers');
var r3 = yield fetch('https://api.github.com/users/github/repos');
console.log([r1.bio, r2[0].login, r3[0].full_name].join('\n'));
}為了獲得最終的執(zhí)行結果,你可能要寫成:
var g = gen();
var result1 = g.next();
result1.value.then(function(data){
return data.json();
})
.then(function(data){
return g.next(data).value;
})
.then(function(data){
return data.json();
})
.then(function(data){
return g.next(data).value
})
.then(function(data){
return data.json();
})
.then(function(data){
g.next(data)
});但我知道你肯定不想寫成這樣……
其實,利用遞歸,我們可以這樣寫:
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value.then(function(data) {
return data.json();
}).then(function(data) {
next(data);
});
}
next();
}
run(gen);其中的關鍵就是 yield 的時候返回一個 Promise 對象,給這個 Promise 對象添加 then 方法,當異步操作成功時執(zhí)行 then 中的 onFullfilled 函數(shù),onFullfilled 函數(shù)中又去執(zhí)行 g.next,從而讓 Generator 繼續(xù)執(zhí)行,然后再返回一個 Promise,再在成功時執(zhí)行 g.next,然后再返回……
啟動器函數(shù)
在 run 這個啟動器函數(shù)中,我們在 then 函數(shù)中將數(shù)據(jù)格式化 data.json(),但在更廣泛的情況下,比如 yield 直接跟一個 Promise,而非一個 fetch 函數(shù)返回的 Promise,因為沒有 json 方法,代碼就會報錯。所以為了更具備通用性,連同這個例子和啟動器,我們修改為:
var fetch = require('node-fetch');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
var r2 = yield fetch('https://api.github.com/users/github/followers');
var json2 = yield r2.json();
var r3 = yield fetch('https://api.github.com/users/github/repos');
var json3 = yield r3.json();
console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value.then(function(data) {
next(data);
});
}
next();
}
run(gen);只要 yield 后跟著一個 Promise 對象,我們就可以利用這個 run 函數(shù)將 Generator 函數(shù)自動執(zhí)行。
回調(diào)函數(shù)
yield 后一定要跟著一個 Promise 對象才能保證 Generator 的自動執(zhí)行嗎?如果只是一個回調(diào)函數(shù)呢?我們來看個例子:
首先我們來模擬一個普通的異步請求:
function fetchData(url, cb) {
setTimeout(function(){
cb({status: 200, data: url})
}, 1000)
}我們將這種函數(shù)改造成:
function fetchData(url) {
return function(cb){
setTimeout(function(){
cb({status: 200, data: url})
}, 1000)
}
}對于這樣的 Generator 函數(shù):
function* gen() {
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}如果要獲得最終的結果:
var g = gen();
var r1 = g.next();
r1.value(function(data) {
var r2 = g.next(data);
r2.value(function(data) {
g.next(data);
});
});如果寫成這樣的話,我們會面臨跟第一節(jié)同樣的問題,那就是當使用多個 yield 時,代碼會循環(huán)嵌套起來……
同樣利用遞歸,所以我們可以將其改造為:
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value(next);
}
next();
}
run(gen);run
由此可以看到 Generator 函數(shù)的自動執(zhí)行需要一種機制,即當異步操作有了結果,能夠自動交回執(zhí)行權。
而兩種方法可以做到這一點。
(1)回調(diào)函數(shù)。將異步操作進行包裝,暴露出回調(diào)函數(shù),在回調(diào)函數(shù)里面交回執(zhí)行權。
(2)Promise 對象。將異步操作包裝成 Promise 對象,用 then 方法交回執(zhí)行權。
在兩種方法中,我們各寫了一個 run 啟動器函數(shù),那我們能不能將這兩種方式結合在一些,寫一個通用的 run 函數(shù)呢?我們嘗試一下:
// 第一版
function run(gen) {
var gen = gen();
function next(data) {
var result = gen.next(data);
if (result.done) return;
if (isPromise(result.value)) {
result.value.then(function(data) {
next(data);
});
} else {
result.value(next)
}
}
next()
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
module.exports = run;其實實現(xiàn)的很簡單,判斷 result.value 是否是 Promise,是就添加 then 函數(shù),不是就直接執(zhí)行。
return Promise
我們已經(jīng)寫了一個不錯的啟動器函數(shù),支持 yield 后跟回調(diào)函數(shù)或者 Promise 對象。
現(xiàn)在有一個問題需要思考,就是我們?nèi)绾潍@得 Generator 函數(shù)的返回值呢?又如果 Generator 函數(shù)中出現(xiàn)了錯誤,就比如 fetch 了一個不存在的接口,這個錯誤該如何捕獲呢?
這很容易讓人想到 Promise,如果這個啟動器函數(shù)返回一個 Promise,我們就可以給這個 Promise 對象添加 then 函數(shù),當所有的異步操作執(zhí)行成功后,我們執(zhí)行 onFullfilled 函數(shù),如果有任何失敗,就執(zhí)行 onRejected 函數(shù)。
我們寫一版:
// 第二版
function run(gen) {
var gen = gen();
return new Promise(function(resolve, reject) {
function next(data) {
try {
var result = gen.next(data);
} catch (e) {
return reject(e);
}
if (result.done) {
return resolve(result.value)
};
var value = toPromise(result.value);
value.then(function(data) {
next(data);
}, function(e) {
reject(e)
});
}
next()
})
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
function toPromise(obj) {
if (isPromise(obj)) return obj;
if ('function' == typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
module.exports = run;與第一版有很大的不同:
首先,我們返回了一個 Promise,當 result.done 為 true 的時候,我們將該值 resolve(result.value),如果執(zhí)行的過程中出現(xiàn)錯誤,被 catch 住,我們會將原因 reject(e)。
其次,我們會使用 thunkToPromise 將回調(diào)函數(shù)包裝成一個 Promise,然后統(tǒng)一的添加 then 函數(shù)。在這里值得注意的是,在 thunkToPromise 函數(shù)中,我們遵循了 error first 的原則,這意味著當我們處理回調(diào)函數(shù)的情況時:
// 模擬數(shù)據(jù)請求
function fetchData(url) {
return function(cb) {
setTimeout(function() {
cb(null, { status: 200, data: url })
}, 1000)
}
}在成功時,第一個參數(shù)應該返回 null,表示沒有錯誤原因。
優(yōu)化
我們在第二版的基礎上將代碼寫的更加簡潔優(yōu)雅一點,最終的代碼如下:
// 第三版
function run(gen) {
return new Promise(function(resolve, reject) {
if (typeof gen == 'function') gen = gen();
// 如果 gen 不是一個迭代器
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);
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise(ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise ' +
'but the following object was passed: "' + String(ret.value) + '"'));
}
})
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
function toPromise(obj) {
if (isPromise(obj)) return obj;
if ('function' == typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
module.exports = run;co
如果我們再將這個啟動器函數(shù)寫的完善一些,我們就相當于寫了一個 co,實際上,上面的代碼確實是來自于 co……
而 co 是什么? co 是大神 TJ Holowaychuk 于 2013 年 6 月發(fā)布的一個小模塊,用于 Generator 函數(shù)的自動執(zhí)行。
如果直接使用 co 模塊,這兩種不同的例子可以簡寫為:
// yield 后是一個 Promise
var fetch = require('node-fetch');
var co = require('co');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
var r2 = yield fetch('https://api.github.com/users/github/followers');
var json2 = yield r2.json();
var r3 = yield fetch('https://api.github.com/users/github/repos');
var json3 = yield r3.json();
console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}
co(gen);// yield 后是一個回調(diào)函數(shù)
var co = require('co');
function fetchData(url) {
return function(cb) {
setTimeout(function() {
cb(null, { status: 200, data: url })
}, 1000)
}
}
function* gen() {
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
co(gen);關于如何在ES6中自動執(zhí)行Generator就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
網(wǎng)頁標題:如何在ES6中自動執(zhí)行Generator-創(chuàng)新互聯(lián)
本文路徑:http://www.yijiale78.com/article42/cesihc.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、網(wǎng)站排名、外貿(mào)建站、網(wǎng)頁設計公司、自適應網(wǎng)站、定制開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)