掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
其最大特點就是可以交出函數(shù)的執(zhí)行權(quán)(即暫停執(zhí)行):

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、成都微信小程序、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了柴桑免費建站歡迎大家使用!
備注:yield 只能在 Generator 中使用,在其他地方使用會報錯。
以下用一段代碼作為示例說明:
function* genDemo() {
console.log('hello before')
const hello = yield 'hello'
console.log('hello after', hello)
yield 'world'
return 'end'
}運行結(jié)果如下:
注意:以上結(jié)果中,變量 hello 打印出來的值為 undefined。說明沒有被賦值。那么該怎么給賦值呢?
應(yīng)該在執(zhí)行 next 方法的時候傳參來賦值。通過這種方法向 Generator 函數(shù)體內(nèi)輸入數(shù)據(jù)。
以下是運行示例:
Generator 函數(shù)內(nèi)部還可以部署錯誤處理代碼,捕獲函數(shù)體外拋出的錯誤。以下是示例代碼和運行結(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)
}
}運行 g.throw 拋出錯誤。Generator 函數(shù)內(nèi)部的 try/catch 就可以捕獲到錯誤。發(fā)生錯誤后, Generator 函數(shù)就結(jié)束運行了,返回 { value: undefined, done: true }。
相對于異步,我們的思維更容易理解同步代碼。異步編程的語法目標是讓代碼變得更像同步代碼。
比如以下代碼(這里假設(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ù)的方式。這種有先后順序的時候,會出現(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)
})這種鏈式調(diào)用的方式,邏輯上清晰了不少。
我們計劃使用 Generator 函數(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í)行流程進行變更,是控制函數(shù)執(zhí)行流程用的,而恰好這個控制流程的機制能夠簡化回調(diào)函數(shù)和 promise 的調(diào)用。
我們現(xiàn)在要做的就是寫一個方法,用來自動控制 Generator 函數(shù)的流程,接收和交還程序的執(zhí)行權(quán)。
在這之前,先了解一下 thunk 函數(shù)。
最早的 thunk 函數(shù)起源于 “傳值調(diào)用”和“傳名調(diào)用”之爭。
let x = 1
function fn(m) {
return m * 2
}
fn(x + 1)
Javascript 是“傳值調(diào)用”。在 JavaScript 語言中,Thunk 函數(shù)替換的不是表達式,而是多參數(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) {
// ...
})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í)行方法如下:
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)思路與回調(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)有個成熟的函數(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)
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ù)的改進,體現(xiàn)在以下幾點:

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流