ES6 異步進(jìn)階第二步:Generator 函數(shù)

一踢星、什么是生成器 Generator橘沥?

生成器對象是由一個 Generator 函數(shù)返回的,并且她符合 可迭代協(xié)議和迭代器協(xié)議桩撮。

語法:
function * gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen(); // "Generator { }"

生成器函數(shù)有以下四個特征:

  • 在 function 關(guān)鍵字和函數(shù)名之間多了一個星號
  • 函數(shù)內(nèi)部使用了 yield 表達(dá)式炮沐,用于定義 Generator 函數(shù)中的每個狀態(tài)
  • Generator 函數(shù)通過多個 yield 表達(dá)式定義內(nèi)部狀態(tài)争群,掉用 Generator 函數(shù)時,不會像普通函數(shù)會立即執(zhí)行大年,而是返回的是一個 Iterator 對象换薄,通過調(diào)用 next() 方法,可以依次遍歷 Generator 函數(shù)的每個內(nèi)部狀態(tài)
  • Generator 函數(shù)的星號的位置有四種情況 如下
function *gen () {}   
function* gen () {}
function * gen () {}
function*gen () {}

一般的寫法是第二種鲜戒。

二专控、如何使用 Generator?

1遏餐、yield 表達(dá)式

yield 關(guān)鍵字在 Generator 函數(shù)中有兩個作用:定義內(nèi)部狀態(tài)和暫停執(zhí)行伦腐,代碼解釋如下:

function *gen () {
  yield 1
  yield 2
  return 3
}

const g = gen()   // Iterator對象
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: true}
g.next() // {value: undefined, done: true}

結(jié)合上述代碼和第一節(jié)的Generator 函數(shù)的特征可知:
在調(diào)用 gen 函數(shù)時,返回的一個 Iterator 對象失都,要想獲得每個 yield 表達(dá)式的狀態(tài)柏蘑,需要調(diào)用 next 方法。
每次調(diào)用 next 方法時粹庞,都會返回一個包含 value 屬性和 done 屬性的對象咳焚,value 屬性表示 yield 表達(dá)式的值,done 屬性是一個布爾值庞溜,表示遍歷是否結(jié)束革半。
如果 value 沒有返回值或者 返回的是 undefined ,說明是函數(shù)運行結(jié)束了流码。
還有一種情況需要注意的額是:yield 表達(dá)式 如果用在另一個表達(dá)式中又官,需要為其加上圓括號“()”,作為函數(shù)參數(shù)和語句時可以不適用圓括號

function *gen () {
  console.log('hello' + yield) ×
  console.log('hello' + (yield)) √
  console.log('hello' + yield '凱斯') ×
  console.log('hello' + (yield '凱斯')) √
  foo(yield 1)  √
  const param = yield 2  √
}
2漫试、yield* 表達(dá)式
yield* 表達(dá)式的使用場景:在一個 Generator 函數(shù)中調(diào)用另一個 Generator 函數(shù)六敬。下面代碼是不能執(zhí)行的:
function *foo () {
  yield 1
}
function *gen () {
  foo()
  yield 2
}
const g = gen()
g.next()  // {value: 2, done: false}
g.next()  // {value: undefined, done: true}

如果使用 yield 表達(dá)式,value 返回的值是一個遍歷器對象:

function *foo () {
  yield 1
}
function *gen () {
  yield foo()
  yield 2
}
const g = gen()
g.next()   // {value: Generator, done: false}
g.next()   // {value: 2, done: false}
g.next()   // {value: undefined, done: true}

這里要實現(xiàn)上面場景驾荣,要使用 yield* 表達(dá)式外构,其實這個表達(dá)式就是 for...of 方法的簡寫:

function *foo () {
  yield 1
}
function *gen () {
  yield* foo()
  yield 2
}
const g = gen()
g.next()   // {value: 1, done: false}
g.next()   // {value: 2, done: false}
g.next()   // {value: undefined, done: true}

// 相當(dāng)于
function *gen () {
  yield 1
  yield 2
}

// 相當(dāng)于
function *gen () {
  for (let item of foo()) {
    yield item
  }
  yield 2
}

yield* 遍歷具有 Iterator 接口的數(shù)據(jù)類型:

const arr = ['a', 'b']
const str = 'yuan'
function *gen () {
  yield arr
  yield* arr
  yield str
  yield* str
}
const g = gen()
g.next() // {value: ['a', 'b'], done: false}
g.next() // {value: 'a', done: false}
g.next() // {value: 'b', done: false}
g.next() // {value: 'yuan', done: false}
g.next() // {value: 'y', done: false}
...

使用 yield* 表達(dá)式取出嵌套數(shù)組中的成員:

// 普通方法
const arr = [1, [[2, 3], 4]]
const str = arr.toString().replace(/,/g, '')
for (let item of str) {
  console.log(+item)      // 1, 2, 3, 4
}

// 使用yield*表達(dá)式
function *gen (arr) {
  if (Array.isArray(arr)) {
    for (let i = 0; i < arr.length; i++) {
      yield * gen(arr[i])
    }
  } else {
    yield arr
  }
}
const g = gen([1, [[2, 3], 4]])
for (let item of g) {
  console.log(item)       // 1, 2, 3, 4
}
3、next 方法

next 方法的作用是分階段執(zhí)行 Generator 函數(shù)播掷。
每一次調(diào)用next方法审编,就會從函數(shù)頭部或者上一次停下來的地方開始執(zhí)行,直到遇到下一個yield表達(dá)式(return 語句)為止歧匈。同時割笙,調(diào)用next方法時,會返回包含value和done屬性的對象,value屬性值可以為yield表達(dá)式伤溉、return語句后面的值或者undefined值,done屬性表示遍歷是否結(jié)束妻率。
遍歷器對象 next 方法的執(zhí)行邏輯如下:

  1. 遇到y(tǒng)ield表達(dá)式乱顾,就暫停執(zhí)行后面的操作,并將緊跟在yield表達(dá)式后面的那個表達(dá)式的值宫静,作為返回的對象的value屬性值走净。
  2. 下一次調(diào)用next方法時,再繼續(xù)往下執(zhí)行孤里,直到遇到下一個yield表達(dá)式。
  3. 如果沒有再遇到新的yield表達(dá)式,就一直運行到函數(shù)結(jié)束陵霉,直到遇到return語句為止貌夕,并將return語句后面表達(dá)式的值,作為返回的對象的value屬性值虏等。
  4. 如果該函數(shù)沒有return語句弄唧,則返回的對象的value屬性值為undefined。
function *gen () {
  yield 1
  yield 2
  return 3
}

const g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: true}
g.next() // {value: undefined, done: true}

根據(jù)next運行邏輯再針對這個例子霍衫,就很容易理解了候引。調(diào)用gen函數(shù),返回遍歷器對象敦跌。
第一次調(diào)用next方法時澄干,在遇到第一個yield表達(dá)式時停止執(zhí)行,value屬性值為1柠傍,即yield表達(dá)式后面的值麸俘,done為false表示遍歷沒有結(jié)束;
第二次調(diào)用next方法時携兵,從暫停的yield表達(dá)式后開始執(zhí)行疾掰,直到遇到下一個yield表達(dá)式后暫停執(zhí)行,value屬性值為2徐紧,done為false静檬;
第三次調(diào)用next方法時,從上一次暫停的yield表達(dá)式后開始執(zhí)行并级,由于后面沒有yield表達(dá)式了拂檩,所以遇到return語句時函數(shù)執(zhí)行結(jié)束,value屬性值為return語句后面的值嘲碧,done屬性值為true表示已經(jīng)遍歷完畢了稻励。
第四次調(diào)用next方法時,value屬性值就是undefined了,此時done屬性為true表示遍歷完畢望抽。以后再調(diào)用next方法都會是這兩個值加矛。

4、next 方法的參數(shù)

yield 語句本身沒有返回值煤篙,或者說總是返回 undefined斟览。

function *gen () {
  var x = yield 'hello world'
  var y = x / 2
  return [x, y]
}
const g = gen()
g.next()    // {value: 'hello world', done: false}
g.next()    // {value: [undefined, NaN], done: true}

由上,第二次調(diào)用 next 方法時辑奈,并沒有返回相應(yīng)的值苛茂,而是返回 undefined,解決方法:給第二次調(diào)用的 next 方法傳入一個參數(shù)鸠窗,該參數(shù)會當(dāng)作上一個 yield 語句的返回值妓羊。

function *gen () {
  var x = yield 'hello world'
  var y = x / 2
  return [x, y]
}
const g = gen()
g.next()    // {value: 'hello world', done: false}
g.next(10)    // {value: [10, 5], done: true}```

注意,這里的 next 方法的參數(shù)是指上一個 yield 語句的返回值稍计,所以在第一次調(diào)用 next 不能傳入?yún)?shù)躁绸。如若要傳參數(shù),需要借用閉包來實現(xiàn)丙猬。

function wrapper (gen) {
  return function (...args) {
    const genObj = gen(...args)
    genObj.next()
    return genObj
  }
}
const generator = wrapper(function *generator () {
  console.log(`hello ${yield}`)
  return 'done'
})
const a = generator().next('keith')
console.log(a)   // hello keith, done

實際上涨颜,yield 表達(dá)式和 next 方法是 generator 函數(shù)的雙向信息傳遞。yield 表達(dá)式向外傳遞 value 值茧球,next 方法的參數(shù)向內(nèi)傳遞值庭瑰。

5、遍歷 Generator 對象
與 Iterator 接口的關(guān)系

任何一個對象的Symbol.iterator屬性抢埋,指向默認(rèn)的遍歷器對象生成函數(shù)弹灭。而Generator函數(shù)也是遍歷器對象生成函數(shù),所以可以將Generator函數(shù)賦值給Symbol.iterator屬性揪垄,這樣就使對象具有了Iterator接口穷吮。默認(rèn)情況下,對象是沒有Iterator接口的饥努。
具有Iterator接口的對象捡鱼,就可以被擴展運算符(...),解構(gòu)賦值酷愧,Array.from和for...of循環(huán)遍歷了驾诈。

function *gen () {
  yield 1
  yield 2
  yield 3
  return 4
}
for (let item of gen()) {
  console.log(item)  // 1 2 3
}

for...of 循環(huán)可以自動遍歷Generator函數(shù)生成的Iterator對象,不用調(diào)用next方法溶浴。

三乍迄、應(yīng)用實例

1、協(xié)程

所謂協(xié)程:是指多個線程相互協(xié)作士败,完成異步任務(wù)闯两。
Generator 函數(shù)是協(xié)程在 ES6 中的實現(xiàn),最大特點就是可以交出函數(shù)的執(zhí)行權(quán)。
整個Generator 函數(shù)就是一個封裝的異步任務(wù)漾狼,或者說是異步任務(wù)的容器重慢。異步操作需要暫停的地方都用 yield 語句注明。Generator 函數(shù)實現(xiàn)協(xié)程代碼如下:

function* gen() {
  var y = yield x + 2;
  return y;
}
var g = gen(1);
g.next(); // { value: 3, done: false };
g.next(); // { value: undefined; done: true }

2逊躁、Thunk 函數(shù)

Thunk 函數(shù)是自動執(zhí)行 Generator 函數(shù)的一種方法伤锚。
Thunk 函數(shù)的含義:起源于“傳名調(diào)用”的“求職策略”。將參數(shù)放到一個臨時函數(shù)之中志衣,在將這個臨時參數(shù)傳入函數(shù)體。這個臨時函數(shù)就是 Thunk 函數(shù)猛们。
在 JavaScript 語言中念脯,Thunk 函數(shù)替換的不是表達(dá)式,而是多參數(shù)函數(shù)弯淘,將其替換成一個只接受回調(diào)函數(shù)作為參數(shù)的單參數(shù)函數(shù)绿店。
任何函數(shù),只要參數(shù)有回調(diào)函數(shù)庐橙,都能寫成 Thenk 函數(shù)的形式假勿。
Thunk 函數(shù)轉(zhuǎn)換器:

// ES5 版本
var Thunk = function(fn) {
  return function () {
    var args = Array.prototype.slice.call(arguments);
    return function (callback) {
      args.push(callback);
      return fn.apply(this, arg);
    }
  }
};

// ES6 版本
const Thunk = function(fn) {
  return function(...args) {
  return function(callback) {
      return fn.call(this, ...args, callback);
    }
  }
}

使用上面的轉(zhuǎn)換器,生成态鳖, fs.redFile 的 Thunk 函數(shù)转培。

var readFileThunk = Thunk(fs.readFile);
redadFileThunk(fileA)(callback);`

Thunk 函數(shù)真正的威力在于可以自動執(zhí)行 Generator 函數(shù)。

function run(fn) {
  var gen = fn();
  function next(err, data) {
    var result = gen.next(data);
    if(result.done) return;
    result.value(next);
  }
  next();
}
function* g() {
  ...
}

run(g);

上面是一個基于 Thunk 函數(shù)的 Generator 執(zhí)行器浆竭,可以在generator 函數(shù)內(nèi)部執(zhí)行多個異步操作浸须。但是,這個里的每一個異步操作都必須是 Thunk 函數(shù)邦泄,也就是說删窒,跟在 yield 表達(dá)式后面的必須是 Thunk 函數(shù)。

3顺囊、Co 模塊

co 模塊是用于 Generator 函數(shù)自動執(zhí)行的一個小工具肌索。
co 模塊是講兩種自動執(zhí)行器(Thunk 函數(shù)和 Promise 對象)包裝成一個模塊。使用 co 模塊的前提條件是特碳,Generator 函數(shù)的 yield 名利后面只是 Thunk 函數(shù)或 Promise 對象诚亚。
使用 co 模塊處理并發(fā)的異步操作 :
1、并發(fā)操作放在數(shù)組里

co(function* () {
  var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(res);
}).catch(onerror);

2测萎、并發(fā)操作寫在對象里

co(function* () {
  var res = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2)
  };
  console.log(res);
}).catch(onerror);

3亡电、處理多個 generator 異步操作

co(function* () {
  var valuse = [n1, n2, n3];
  yield values.map(somethingAsync);
});

function* somethingAsync(x) {
  // do something async
  return y;
}

上面代碼允許并發(fā) 3 個 somethingAsync 異步操作。

四硅瞧、結(jié)語

本章需要了解的是生成器函數(shù) Generator 的具體含義及作用份乒,如何使用 Generator 函數(shù),以及 Generator 函數(shù)的幾個特殊的應(yīng)用實例。

戳我博客

章節(jié)目錄

1或辖、ES6中啥是塊級作用域瘾英?運用在哪些地方?
2颂暇、ES6中使用解構(gòu)賦值能帶給我們什么缺谴?
3、ES6字符串?dāng)U展增加了哪些耳鸯?
4湿蛔、ES6對正則做了哪些擴展?
5县爬、ES6數(shù)值多了哪些擴展阳啥?
6、ES6函數(shù)擴展(箭頭函數(shù))
7财喳、ES6 數(shù)組給我們帶來哪些操作便利察迟?
8、ES6 對象擴展
9耳高、Symbol 數(shù)據(jù)類型在 ES6 中起什么作用扎瓶?
10、Map 和 Set 兩數(shù)據(jù)結(jié)構(gòu)在ES6的作用
11泌枪、ES6 中的Proxy 和 Reflect 到底是什么鬼概荷?
12、從 Promise 開始踏入異步操作之旅
13工闺、ES6 迭代器(Iterator)和 for...of循環(huán)使用方法
14乍赫、ES6 異步進(jìn)階第二步:Generator 函數(shù)
15、JavaScript 異步操作進(jìn)階第三步:async 函數(shù)
16陆蟆、ES6 構(gòu)造函數(shù)語法糖:class 類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雷厂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叠殷,更是在濱河造成了極大的恐慌改鲫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件林束,死亡現(xiàn)場離奇詭異像棘,居然都是意外死亡,警方通過查閱死者的電腦和手機壶冒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門缕题,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胖腾,你說我怎么就攤上這事烟零”袼桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵锨阿,是天一觀的道長宵睦。 經(jīng)常有香客問我,道長墅诡,這世上最難降的妖魔是什么壳嚎? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮末早,結(jié)果婚禮上烟馅,老公的妹妹穿的比我還像新娘。我一直安慰自己然磷,他們只是感情好焙糟,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著样屠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缺脉。 梳的紋絲不亂的頭發(fā)上痪欲,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音攻礼,去河邊找鬼业踢。 笑死,一個胖子當(dāng)著我的面吹牛礁扮,可吹牛的內(nèi)容都是我干的知举。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼太伊,長吁一口氣:“原來是場噩夢啊……” “哼雇锡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起僚焦,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤锰提,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后芳悲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體立肘,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年名扛,在試婚紗的時候發(fā)現(xiàn)自己被綠了谅年。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡肮韧,死狀恐怖融蹂,靈堂內(nèi)的尸體忽然破棺而出旺订,到底是詐尸還是另有隱情,我是刑警寧澤殿较,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布耸峭,位于F島的核電站,受9級特大地震影響淋纲,放射性物質(zhì)發(fā)生泄漏劳闹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一洽瞬、第九天 我趴在偏房一處隱蔽的房頂上張望本涕。 院中可真熱鬧,春花似錦伙窃、人聲如沸菩颖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晦闰。三九已至,卻和暖如春鳍怨,著一層夾襖步出監(jiān)牢的瞬間呻右,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工鞋喇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留声滥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓侦香,卻偏偏與公主長得像落塑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子罐韩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要憾赁。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程散吵,根本...
    呼呼哥閱讀 7,301評論 5 22
  • 在此處先列下本篇文章的主要內(nèi)容 簡介 next方法的參數(shù) for...of循環(huán) Generator.prototy...
    醉生夢死閱讀 1,439評論 3 8
  • 簡介 基本概念 Generator函數(shù)是ES6提供的一種異步編程解決方案缠沈,語法行為與傳統(tǒng)函數(shù)完全不同。本章詳細(xì)介紹...
    呼呼哥閱讀 1,070評論 0 4
  • 筆記错蝴,總結(jié)摘錄自阮一峰筆記中有不少自己看書的總結(jié) 基本概念 核心目的:異步編程解決方案 關(guān)鍵概念:狀態(tài)機洲愤,執(zhí)行權(quán)限...
    布蕾布蕾閱讀 5,103評論 0 4
  • 今天在群里看見一個引導(dǎo)頁的效果,感覺好不錯顷锰,于是就擼了下柬赐,先看看效果圖 我們可以看到,當(dāng)我們滑動頁面的時候官紫,會有一...
    ReturnYHH閱讀 2,032評論 0 3