新聞中心
理解 Generator
其最大特點(diǎn)就是可以交出函數(shù)的執(zhí)行權(quán)(即暫停執(zhí)行):

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、成都微信小程序、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了柴桑免費(fèi)建站歡迎大家使用!
- 定義時(shí)在 function 后面有一個*。
- 可以使用關(guān)鍵字 yield 進(jìn)行多次返回。
- 調(diào)用后并不立即執(zhí)行,而是返回一個指向內(nèi)部狀態(tài)的指針對象,該對象是一個遍歷器(Iterator)對象。
- 調(diào)用返回遍歷器對象的 next 方法,會移動內(nèi)部指針,使得指針指向下一個狀態(tài)。會返回一個對象,表示當(dāng)前階段的信息。其中 value 屬性是 yield 語句后面表達(dá)式的值,表示當(dāng)前階段的值;done 屬性表示 Generator 函數(shù)是否執(zhí)行完畢,即是否還有下一個階段。
- 返回遍歷器對象有個 throw 方法可以拋出錯誤,拋出的錯誤可以被函數(shù)體內(nèi)的 try/catch 代碼塊捕獲。
備注:yield 只能在 Generator 中使用,在其他地方使用會報(bào)錯。
以下用一段代碼作為示例說明:
function* genDemo() {
console.log('hello before')
const hello = yield 'hello'
console.log('hello after', hello)
yield 'world'
return 'end'
}運(yùn)行結(jié)果如下:
- 執(zhí)行 genDemo 這個 Generator 函數(shù),返回的 g 是一個指針。這個時(shí)候沒有任何打印,函數(shù)體內(nèi)代碼沒有執(zhí)行。
- 執(zhí)行 g.next() 運(yùn)行到第一個 yield 處停止運(yùn)行,返回 { value: ‘hello’, done: false }。value 值表示的是 yield 后面的運(yùn)行結(jié)果,done 表示的是整個 Generator 是否執(zhí)行完畢。
- 繼續(xù)執(zhí)行 g.next(),到第二個 yield 處停止運(yùn)行,后面還有代碼,所以done 為false。
- 繼續(xù)執(zhí)行 g.next(), 執(zhí)行到 return 了,代碼執(zhí)行完畢,所以 done 為 true。
- 繼續(xù)執(zhí)行 g.next(), 因?yàn)橐呀?jīng)執(zhí)行完畢了,繼續(xù)執(zhí)行的話,都會返回 { value: undefined, done: true }。
注意:以上結(jié)果中,變量 hello 打印出來的值為 undefined。說明沒有被賦值。那么該怎么給賦值呢?
應(yīng)該在執(zhí)行 next 方法的時(shí)候傳參來賦值。通過這種方法向 Generator 函數(shù)體內(nèi)輸入數(shù)據(jù)。
以下是運(yùn)行示例:
- 第一次執(zhí)行 g.next,運(yùn)行到第一個 yield 處停止運(yùn)行。這個時(shí)候 hello 變量還沒有被賦值。
- 第二次執(zhí)行 g.next 時(shí),傳一個參數(shù) ’wmm66’,會先將這個傳入的參數(shù)賦值給上一次暫停時(shí) yield 前面要賦值的變量,即上面代碼中的 hello 變量。賦值完成后再往下運(yùn)行代碼,運(yùn)行到下一個 yield 處。
Generator 函數(shù)內(nèi)部還可以部署錯誤處理代碼,捕獲函數(shù)體外拋出的錯誤。以下是示例代碼和運(yùn)行結(jié)果。
function* genDemo() {
try {
console.log('hello before')
const hello = yield 'hello'
console.log('hello after', hello)
yield 'world'
return 'end'
} catch(err) {
console.warn(err)
}
}運(yùn)行 g.throw 拋出錯誤。Generator 函數(shù)內(nèi)部的 try/catch 就可以捕獲到錯誤。發(fā)生錯誤后, Generator 函數(shù)就結(jié)束運(yùn)行了,返回 { value: undefined, done: true }。
Generator 實(shí)現(xiàn)異步編程
相對于異步,我們的思維更容易理解同步代碼。異步編程的語法目標(biāo)是讓代碼變得更像同步代碼。
比如以下代碼(這里假設(shè)邏輯上要求讀取完 test1.txt,然后再讀取 test2.txt)。
const fs = require('fs')
fs.readFile('./test1.txt', function(err, data1) {
if (err) return err
console.log(data1.toString())
fs.readFile('./test2.txt', function(err, data2) {
if (err) return err
console.log(data2.toString())
})
})該代碼是回調(diào)函數(shù)的方式。這種有先后順序的時(shí)候,會出現(xiàn)多個函數(shù)嵌套,造成閱讀和理解上的障礙。也就是常說的“回調(diào)地獄”。
我們把它改成 promise 的寫法。
const fs = require('fs')
const promiseReadFile = (file) => {
return new Promise((resolve, reject) => {
fs.readFile(file, function(err, data) {
if (err) return reject(err)
resolve(data)
})
})
}
promiseReadFile('./test1.txt')
.then(data1 => {
console.log(data1.toString())
})
.then(() => {
return promiseReadFile('./test2.txt')
})
.then(data2 => {
console.log(data2.toString())
}).catch(err => {
console.error(err)
})這種鏈?zhǔn)秸{(diào)用的方式,邏輯上清晰了不少。
我們計(jì)劃使用 Generator 函數(shù),通過如下編碼實(shí)現(xiàn)異步。
const genReadFile = function* (){
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}Generator 函數(shù)和 yield 本身跟異步?jīng)]有關(guān)系。yield 是對函數(shù)的執(zhí)行流程進(jìn)行變更,是控制函數(shù)執(zhí)行流程用的,而恰好這個控制流程的機(jī)制能夠簡化回調(diào)函數(shù)和 promise 的調(diào)用。
我們現(xiàn)在要做的就是寫一個方法,用來自動控制 Generator 函數(shù)的流程,接收和交還程序的執(zhí)行權(quán)。
在這之前,先了解一下 thunk 函數(shù)。
thunk函數(shù)介紹
最早的 thunk 函數(shù)起源于 “傳值調(diào)用”和“傳名調(diào)用”之爭。
let x = 1
function fn(m) {
return m * 2
}
fn(x + 1)
- 傳值調(diào)用的主張:執(zhí)行前就進(jìn)行計(jì)算。先計(jì)算 x + 1 = 2,然后將值2傳入fn 方法中:m * 2。
- 傳名調(diào)用的主張:只在執(zhí)行的時(shí)候才進(jìn)行計(jì)算。將 x + 1 直接傳入到fn方法中:(x + 1) * 2。
Javascript 是“傳值調(diào)用”。在 JavaScript 語言中,Thunk 函數(shù)替換的不是表達(dá)式,而是多參數(shù)函數(shù),將其替換成單參數(shù)的版本,且只接受回調(diào)函數(shù)作為參數(shù)。
// 正常版本的readFile
const fn = fs.readFile(file, callback)
// thunk版本的readFile
function Thunk(file) {
return function(callback) {
return fn(file, callback)
}
}
const thunkReadFile = Thunk(file)
thunkReadFile(callback)
該代碼中的 thunkReadFile 函數(shù),只接受回調(diào)函數(shù)作為參數(shù)。這個單參數(shù)版本,就叫做 Thunk 函數(shù)。
任何函數(shù)只要存在回調(diào)就可以 thunk 轉(zhuǎn)換,下面是一個簡單的 thunk 轉(zhuǎn)換器。
const Thunk = function(fn) {
return function() {
const args = Array.prototype.slice.call(arguments)
return function(callback) {
args.push(callback)
return fn.apply(this, args)
}
}
}使用上面的轉(zhuǎn)換器,生成 fs.readFile 的 Thunk 函數(shù)。
const readFileThunk = Thunk(fs.readFile)
readFileThunk(file)(callback)
有個插件 thunkify 可以直接轉(zhuǎn)換。
const fs = require('fs')
const thunkify = require('thunkify')
const readFile = thunkify(fs.readFile)
readFile('./test1.txt')(function(err, str) {
// ...
})實(shí)現(xiàn)回調(diào)函數(shù)的異步
Thunk 函數(shù)現(xiàn)在可以用于 Generator 函數(shù)的自動流程管理。我們讓 Generator 函數(shù)的 yield 后面都執(zhí)行 Thunk 函數(shù)。就改造出了我們想要的代碼。
const fs = require('fs')
const thunkify = require('thunkify')
const readFile = thunkify(fs.readFile)
const genReadFile = function* (){
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}那么應(yīng)該怎么執(zhí)行呢?我們先來分析一下:
- 第一次執(zhí)行 next 方法,會返回對象中的 value 是一個函數(shù),該函數(shù)可以傳入一個callback,這個 callback 里面能夠獲取到 ./test1.txt 文件讀出來的數(shù)據(jù)。
- 讀出來 ./test1.txt 文件的數(shù)據(jù)后,我們需要繼續(xù)執(zhí)行 next,此時(shí)需要傳入從 ./test1.txt 文件讀出來的數(shù)據(jù),這樣才能將該數(shù)據(jù)賦值給 Generator 函數(shù)中的 data1 變量。
- 然后同上...
所以執(zhí)行方法如下:
const g = genReadFile()
const r1 = g.next()
r1.value(function(err, data1){
if (err) throw err
const r2 = g.next(data1)
r2.value(function(err, data2){
if (err) throw err
g.next(data2)
})
})
我們寫一個 Generator 自動執(zhí)行器,用來執(zhí)行 Generator 函數(shù)。
function run(gen) {
const g = gen()
function next(err, data) {
const result = g.next(data)
if (result.done) return
result.value(next)
}
next()
}
run(genReadFile)實(shí)現(xiàn) Promise 的異步
思路與回調(diào)函數(shù)相同,具體代碼如下:
const fs = require('fs')
const readFile = (file) = >{
return new Promise((resolve, reject) = >{
fs.readFile(file, function(err, data) {
if (err) return reject(err)
resolve(data)
})
})
}
const genReadFile = function * () {
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}手動執(zhí)行代碼如下:
const g = genReadFile()
g.next().value.then(function(data1) {
g.next(data1).value.then(function(data2) {
g.next(data2)
})
})
Generator 自動執(zhí)行器代碼如下:
function run(gen) {
const g = gen()
function next(data) {
const { value, done } = g.next(data)
if (done) return value
value.then(function(data) {
next(data)
})
}
next()
}
run(genReadFile)co函數(shù)
有個成熟的函數(shù)庫 co。該函數(shù)庫接受 Generator 函數(shù)作為參數(shù),返回一個 Promise 對象。
Thunk 函數(shù)示例:
const fs = require('fs')
const thunkify = require('thunkify')
const co = require('co')
const readFile = thunkify(fs.readFile)
const genReadFile = function * () {
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}
co(genReadFile)
Promise 對象示例:
const fs = require('fs')
const co = require('co')
const readFile = (file) = >{
return new Promise((resolve, reject) = >{
fs.readFile(file, function(err, data) {
if (err) return reject(err)
resolve(data)
})
})
}
const genReadFile = function * () {
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}
co(genReadFile)
Async/await 對比
ES7 引入了 async 函數(shù)。一句話,async 函數(shù)就是 Generator 函數(shù)的語法糖。
Generator 函數(shù)封裝的異步代碼示例:
const genReadFile = function* () {
const data1 = yield readFile('./test1.txt')
console.log(data1.toString())
const data2 = yield readFile('./test2.txt')
console.log(data2.toString())
}
co(genReadFile)改寫成 async 函數(shù)寫法:
const asyncReadFile = async function() {
const data1 = await readFile('./test1.txt')
console.log(data1.toString())
const data2 = await readFile('./test2.txt')
console.log(data2.toString())
}
asyncReadFile()以上兩段代碼對比,async 函數(shù)就是將 Generator 函數(shù)的星號 * 替換成 async,放在 function 關(guān)鍵字前面,將 yield 替換成 await。
async 函數(shù)對 Generator 函數(shù)的改進(jìn),體現(xiàn)在以下幾點(diǎn):
- 內(nèi)置執(zhí)行器:Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器(以上代碼就是使用 co 模塊執(zhí)行),而 async 函數(shù)自帶執(zhí)行器。async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣。
- 更好的語義:async 表示函數(shù)里有異步操作,await 表示緊跟在后面的表達(dá)式需要等待結(jié)果。語義更清晰。
- 更廣的適用性:co 模塊約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise 對象,而 async 函數(shù)的 await 命令后面,可以是Promise 對象和原始類型的值。
- 返回值是 Promise:async 函數(shù)的返回值是 Promise 對象,這比 Generator 函數(shù)的返回值是 Iterator 。 對象方便多了。可以用 then 方法指定下一步的操作。
文章名稱:一文掌握生成器Generator,利用Generator實(shí)現(xiàn)異步編程
新聞來源:http://m.5511xx.com/article/ccdpooc.html


咨詢
建站咨詢
