摘要:將異步操作進(jìn)行包裝,暴露出回調(diào)函數(shù),在回調(diào)函數(shù)里面交回執(zhí)行權(quán)。其次,我們會使用將回調(diào)函數(shù)包裝成一個,然后統(tǒng)一的添加函數(shù)。
單個異步任務(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ù),獲取遍歷器對象。
然后使用 next 方法,執(zhí)行異步任務(wù)的第一階段,即 fetch(url)。
注意,由于 fetch(url) 會返回一個 Promise 對象,所以 result 的值為:
{ value: Promise {}, done: false }
最后我們?yōu)檫@個 Promise 對象添加一個 then 方法,先將其返回的數(shù)據(jù)格式化(data.json()),再調(diào)用 g.next,將獲得的數(shù)據(jù)傳進(jìn)去,由此可以執(zhí)行異步任務(wù)的第二階段,代碼執(zhí)行完畢。
多個異步任務(wù)上節(jié)我們只調(diào)用了一個接口,那如果我們調(diào)用了多個接口,使用了多個 yield,我們豈不是要在 then 函數(shù)中不斷的嵌套下去……
所以我們來看看執(zhí)行多個異步任務(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(" ")); }
為了獲得最終的執(zhí)行結(jié)果,你可能要寫成:
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);
其中的關(guān)鍵就是 yield 的時候返回一個 Promise 對象,給這個 Promise 對象添加 then 方法,當(dāng)異步操作成功時執(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(" ")); } 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(" ")); }
如果要獲得最終的結(jié)果:
var g = gen(); var r1 = g.next(); r1.value(function(data) { var r2 = g.next(data); r2.value(function(data) { g.next(data); }); });
如果寫成這樣的話,我們會面臨跟第一節(jié)同樣的問題,那就是當(dāng)使用多個 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í)行需要一種機(jī)制,即當(dāng)異步操作有了結(jié)果,能夠自動交回執(zhí)行權(quán)。
而兩種方法可以做到這一點。
(1)回調(diào)函數(shù)。將異步操作進(jìn)行包裝,暴露出回調(diào)函數(shù),在回調(diào)函數(shù)里面交回執(zhí)行權(quán)。
(2)Promise 對象。將異步操作包裝成 Promise 對象,用 then 方法交回執(zhí)行權(quán)。
在兩種方法中,我們各寫了一個 run 啟動器函數(shù),那我們能不能將這兩種方式結(jié)合在一些,寫一個通用的 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ù),當(dāng)所有的異步操作執(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,當(dāng) 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 的原則,這意味著當(dāng)我們處理回調(diào)函數(shù)的情況時:
// 模擬數(shù)據(jù)請求 function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) } }
在成功時,第一個參數(shù)應(yīng)該返回 null,表示沒有錯誤原因。
優(yōu)化我們在第二版的基礎(chǔ)上將代碼寫的更加簡潔優(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ù)寫的完善一些,我們就相當(dāng)于寫了一個 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(" ")); } 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(" ")); } co(gen);
是不是特別的好用?
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預(yù)計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標(biāo)簽?zāi)0濉⒓^函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98472.html
摘要:采用的生成非波拉契數(shù)列提供了原生的支持,語法非常有特色,關(guān)鍵字后面緊跟一個星號。的詳細(xì)介紹參考官網(wǎng)先看如何用這個黑科技重新實現(xiàn)非波拉契樹立的生成。在這個內(nèi)部,我們定義了一個無限循環(huán),用于計算非波拉契數(shù)列。 程序員面試系列 Java面試系列-webapp文件夾和WebContent文件夾的區(qū)別? 程序員面試系列:Spring MVC能響應(yīng)HTTP請求的原因? Java程序員面試系列-什么...
摘要:大約后輸出我們直接在官網(wǎng)的粘貼上述代碼,然后查看代碼編譯成什么樣子相關(guān)的代碼我們在系列之將編譯成了什么樣子中已經(jīng)介紹過了,這次我們重點來看看函數(shù)以上這段代碼主要是用來實現(xiàn)的自動執(zhí)行以及返回。 前言 本文就是簡單介紹下 Async 語法編譯后的代碼。 Async const fetchData = (data) => new Promise((resolve) => setTimeout...
摘要:標(biāo)準(zhǔn)引入了函數(shù),使得異步操作變得更加方便。在異步處理上,函數(shù)就是函數(shù)的語法糖。在實際項目中,錯誤處理邏輯可能會很復(fù)雜,這會導(dǎo)致冗余的代碼。的出現(xiàn)使得就可以捕獲同步和異步的錯誤。如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。 async ES2017 標(biāo)準(zhǔn)引入了 async 函數(shù),使得異步操作變得更加方便。 在異步處理上,async 函數(shù)就是 Generator 函數(shù)的語法糖。 ...
摘要:前言本文就是簡單介紹下語法編譯后的代碼。如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎,對作者也是一種鼓勵。 前言 本文就是簡單介紹下 Generator 語法編譯后的代碼。 Generator function* helloWorldGenerator() { yield hello; yield world; return ending...
摘要:在誕生以前,異步編程的方式大概有下面四種回調(diào)函數(shù)事件監(jiān)聽發(fā)布訂閱對象將異步編程帶入了一個全新的階段,中的函數(shù)更是給出了異步編程的終極解決方案。這意味著,出錯的代碼與處理錯誤的代碼,實現(xiàn)了時間和空間上的分離,這對于異步編程無疑是很重要的。 寫在前面 有一個有趣的問題: 為什么Node.js約定回調(diào)函數(shù)的第一個參數(shù)必須是錯誤對象err(如果沒有錯誤,該參數(shù)就是null)? 原因是執(zhí)行回調(diào)函...
閱讀 3190·2021-11-10 11:35
閱讀 1295·2019-08-30 13:20
閱讀 1117·2019-08-29 16:18
閱讀 2131·2019-08-26 13:54
閱讀 2155·2019-08-26 13:50
閱讀 955·2019-08-26 13:39
閱讀 2473·2019-08-26 12:08
閱讀 1951·2019-08-26 10:37