這篇文章將為大家詳細(xì)講解有關(guān)如何在ES6中自動(dòng)執(zhí)行Generator,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
成都創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括鎮(zhèn)沅網(wǎng)站建設(shè)、鎮(zhèn)沅網(wǎng)站制作、鎮(zhèn)沅網(wǎng)頁(yè)制作以及鎮(zhèn)沅網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,鎮(zhèn)沅網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到鎮(zhèn)沅省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!單個(gè)異步任務(wù)
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í)行結(jié)果,你需要這樣做:
var g = gen(); var result = g.next(); result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });
首先執(zhí)行 Generator 函數(shù),獲取遍歷器對(duì)象。
然后使用 next 方法,執(zhí)行異步任務(wù)的第一階段,即 fetch(url)。
注意,由于 fetch(url) 會(huì)返回一個(gè) Promise 對(duì)象,所以 result 的值為:
{ value: Promise { <pending> }, done: false }
最后我們?yōu)檫@個(gè) Promise 對(duì)象添加一個(gè) then 方法,先將其返回的數(shù)據(jù)格式化(data.json()),再調(diào)用 g.next,將獲得的數(shù)據(jù)傳進(jìn)去,由此可以執(zhí)行異步任務(wù)的第二階段,代碼執(zhí)行完畢。
多個(gè)異步任務(wù)
上節(jié)我們只調(diào)用了一個(gè)接口,那如果我們調(diào)用了多個(gè)接口,使用了多個(gè) yield,我們豈不是要在 then 函數(shù)中不斷的嵌套下去……
所以我們來(lái)看看執(zhí)行多個(gè)異步任務(wù)的情況:
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í)行結(jié)果,你可能要寫(xiě)成:
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) });
但我知道你肯定不想寫(xiě)成這樣……
其實(shí),利用遞歸,我們可以這樣寫(xiě):
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);
其中的關(guān)鍵就是 yield 的時(shí)候返回一個(gè) Promise 對(duì)象,給這個(gè) Promise 對(duì)象添加 then 方法,當(dāng)異步操作成功時(shí)執(zhí)行 then 中的 onFullfilled 函數(shù),onFullfilled 函數(shù)中又去執(zhí)行 g.next,從而讓 Generator 繼續(xù)執(zhí)行,然后再返回一個(gè) Promise,再在成功時(shí)執(zhí)行 g.next,然后再返回……
啟動(dòng)器函數(shù)
在 run 這個(gè)啟動(dòng)器函數(shù)中,我們?cè)?then 函數(shù)中將數(shù)據(jù)格式化 data.json(),但在更廣泛的情況下,比如 yield 直接跟一個(gè) Promise,而非一個(gè) fetch 函數(shù)返回的 Promise,因?yàn)闆](méi)有 json 方法,代碼就會(huì)報(bào)錯(cuò)。所以為了更具備通用性,連同這個(gè)例子和啟動(dòng)器,我們修改為:
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 后跟著一個(gè) Promise 對(duì)象,我們就可以利用這個(gè) run 函數(shù)將 Generator 函數(shù)自動(dòng)執(zhí)行。
回調(diào)函數(shù)
yield 后一定要跟著一個(gè) Promise 對(duì)象才能保證 Generator 的自動(dòng)執(zhí)行嗎?如果只是一個(gè)回調(diào)函數(shù)呢?我們來(lái)看個(gè)例子:
首先我們來(lái)模擬一個(gè)普通的異步請(qǐng)求:
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) } }
對(duì)于這樣的 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')); }
如果要獲得最終的結(jié)果:
var g = gen(); var r1 = g.next(); r1.value(function(data) { var r2 = g.next(data); r2.value(function(data) { g.next(data); }); });
如果寫(xiě)成這樣的話,我們會(huì)面臨跟第一節(jié)同樣的問(wèn)題,那就是當(dāng)使用多個(gè) yield 時(shí),代碼會(huì)循環(huán)嵌套起來(lái)……
同樣利用遞歸,所以我們可以將其改造為:
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ù)的自動(dòng)執(zhí)行需要一種機(jī)制,即當(dāng)異步操作有了結(jié)果,能夠自動(dòng)交回執(zhí)行權(quán)。
而兩種方法可以做到這一點(diǎn)。
(1)回調(diào)函數(shù)。將異步操作進(jìn)行包裝,暴露出回調(diào)函數(shù),在回調(diào)函數(shù)里面交回執(zhí)行權(quán)。
(2)Promise 對(duì)象。將異步操作包裝成 Promise 對(duì)象,用 then 方法交回執(zhí)行權(quán)。
在兩種方法中,我們各寫(xiě)了一個(gè) run 啟動(dòng)器函數(shù),那我們能不能將這兩種方式結(jié)合在一些,寫(xiě)一個(gè)通用的 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;
其實(shí)實(shí)現(xiàn)的很簡(jiǎn)單,判斷 result.value 是否是 Promise,是就添加 then 函數(shù),不是就直接執(zhí)行。
return Promise
我們已經(jīng)寫(xiě)了一個(gè)不錯(cuò)的啟動(dòng)器函數(shù),支持 yield 后跟回調(diào)函數(shù)或者 Promise 對(duì)象。
現(xiàn)在有一個(gè)問(wèn)題需要思考,就是我們?nèi)绾潍@得 Generator 函數(shù)的返回值呢?又如果 Generator 函數(shù)中出現(xiàn)了錯(cuò)誤,就比如 fetch 了一個(gè)不存在的接口,這個(gè)錯(cuò)誤該如何捕獲呢?
這很容易讓人想到 Promise,如果這個(gè)啟動(dòng)器函數(shù)返回一個(gè) Promise,我們就可以給這個(gè) Promise 對(duì)象添加 then 函數(shù),當(dāng)所有的異步操作執(zhí)行成功后,我們執(zhí)行 onFullfilled 函數(shù),如果有任何失敗,就執(zhí)行 onRejected 函數(shù)。
我們寫(xiě)一版:
// 第二版 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;
與第一版有很大的不同:
首先,我們返回了一個(gè) Promise,當(dāng) result.done 為 true 的時(shí)候,我們將該值 resolve(result.value),如果執(zhí)行的過(guò)程中出現(xiàn)錯(cuò)誤,被 catch 住,我們會(huì)將原因 reject(e)。
其次,我們會(huì)使用 thunkToPromise 將回調(diào)函數(shù)包裝成一個(gè) Promise,然后統(tǒng)一的添加 then 函數(shù)。在這里值得注意的是,在 thunkToPromise 函數(shù)中,我們遵循了 error first 的原則,這意味著當(dāng)我們處理回調(diào)函數(shù)的情況時(shí):
// 模擬數(shù)據(jù)請(qǐng)求 function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) } }
在成功時(shí),第一個(gè)參數(shù)應(yīng)該返回 null,表示沒(méi)有錯(cuò)誤原因。
優(yōu)化
我們?cè)诘诙娴幕A(chǔ)上將代碼寫(xiě)的更加簡(jiǎn)潔優(yōu)雅一點(diǎn),最終的代碼如下:
// 第三版 function run(gen) { return new Promise(function(resolve, reject) { if (typeof gen == 'function') gen = gen(); // 如果 gen 不是一個(gè)迭代器 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
如果我們?cè)賹⑦@個(gè)啟動(dòng)器函數(shù)寫(xiě)的完善一些,我們就相當(dāng)于寫(xiě)了一個(gè) co,實(shí)際上,上面的代碼確實(shí)是來(lái)自于 co……
而 co 是什么? co 是大神 TJ Holowaychuk 于 2013 年 6 月發(fā)布的一個(gè)小模塊,用于 Generator 函數(shù)的自動(dòng)執(zhí)行。
如果直接使用 co 模塊,這兩種不同的例子可以簡(jiǎn)寫(xiě)為:
// yield 后是一個(gè) 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 后是一個(gè)回調(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);
關(guān)于如何在ES6中自動(dòng)執(zhí)行Generator就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
網(wǎng)頁(yè)標(biāo)題:如何在ES6中自動(dòng)執(zhí)行Generator-創(chuàng)新互聯(lián)
本文路徑:http://www.rwnh.cn/article42/cesihc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、網(wǎng)站排名、外貿(mào)建站、網(wǎng)頁(yè)設(shè)計(jì)公司、自適應(yīng)網(wǎng)站、定制開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容