摘要:生成器是原生提供的異步編程方案,其語(yǔ)法行為和傳統(tǒng)函數(shù)完全不同,阮大的入門(mén)一書(shū)中對(duì)生成器有比較詳盡的介紹,還有一些其他的文章可以參考,比如入門(mén)深入淺出三生成器深入淺出十一生成器,續(xù)篇本文主要是通過(guò)一些代碼示例來(lái)記錄和總結(jié)生成器的用法。
Generator
生成器是es6原生提供的異步編程方案,其語(yǔ)法行為和傳統(tǒng)函數(shù)完全不同,阮大的《ECMAScript 6 入門(mén)》一書(shū)中對(duì)生成器有比較詳盡的介紹,還有一些其他的文章可以參考,比如:
《ECMAScript 6 入門(mén):generator》
深入淺出ES6(三):生成器 Generators
深入淺出ES6(十一):生成器 Generators,續(xù)篇
本文主要是通過(guò)一些代碼示例來(lái)記錄和總結(jié)生成器的用法。
yield 和 nextyield和next在生成器中扮演著非常重要的角色,前者是一個(gè)操作符,后者是生成器上的一個(gè)函數(shù)。
他們具有以下特性:
需要調(diào)用generator的next函數(shù),生成器中的語(yǔ)句才開(kāi)始執(zhí)行;
next函數(shù)在生成器之外調(diào)用,意味著可以在生成器之外控制其內(nèi)部操作的執(zhí)行過(guò)程;
當(dāng)生成器執(zhí)行到yield操作符就立即執(zhí)行yield之后的語(yǔ)句并暫停,不敢妄言內(nèi)部原理,姑且感性地比作savepoint;
當(dāng)再次調(diào)用生成器的next函數(shù)時(shí),生成器從上次發(fā)生yield的"savepoint"繼續(xù)執(zhí)行,直到再次遇到yield,或者遇到是return或者throw生成器就退出;
next的返回值是一個(gè)形如{done:false, value:x}的對(duì)象,每次調(diào)用next都會(huì)使生成器繼續(xù)執(zhí)行,對(duì)于next的返回值有如下規(guī)律:
如果再次遇到yield,next返回值中的value屬性是緊接在這條yield之后的語(yǔ)句執(zhí)行之后的返回值;
如果遇到的是return,那么返回對(duì)象done=true,value則是return的返回值;
其他情況下,返回對(duì)象{done:false, value:undefined};
next的輸入?yún)?shù)在上一次發(fā)生yield的地方返回,所以第一次調(diào)用next傳入的參數(shù)是“然并卵”,next是在生成器之外調(diào)用的,所以這個(gè)機(jī)制使得我們有能力控制生成器內(nèi)部的行為。
以上說(shuō)了很多,先看一個(gè)用生成器實(shí)現(xiàn)的一個(gè)無(wú)限斐波那契數(shù)列,可以無(wú)限的調(diào)用next函數(shù),他永遠(yuǎn)不會(huì)返回done=true
const f = function* fibonacci() { let [a, b] = [0, 1]; for (;;) { yield a; [a, b] = [b, a + b]; } }(); //執(zhí)行三次,得到三個(gè)對(duì)象,其value值分別是0,1,1 for (let i of Array(3).keys()) { console.log(f.next()); }
接下來(lái)通過(guò)一段代碼看看next和yield在傳值和返回值上的情況,如下:
const iter = function* gen() { console.log(`yield ${(yield "a" + 0)}`); console.log(`yield ${(yield "b" + 1)}`); return "c" + 2; }(); console.log(`next:${iter.next(0).value}`); //輸出 next:a0 console.log(`next:${iter.next(1).value}`); //輸出 yield 1 next:b1 console.log(`next:${iter.next(2).value}`); //輸出 yield 2 next:c2
對(duì)以上代碼的輸出分析如下:
第一個(gè)next觸發(fā)生成器執(zhí)行到第一個(gè)yield,并立即執(zhí)行"a" + 0 = "a0", a0作為這次next的返回值;
第二個(gè)帶參數(shù)為1的next觸發(fā)生成器繼續(xù)執(zhí)行,此時(shí)第一個(gè)yield才返回1,然后執(zhí)行到第二個(gè)yield并立即立即這條yield后面的"b" + 1 = "b1",b1作為這次next的返回;
第三個(gè)next執(zhí)行以此類推……
異步編程方案在同步編程模型中,每個(gè)函數(shù)總是有序依次地執(zhí)行,一般上一個(gè)函數(shù)執(zhí)行的結(jié)果往往是下一個(gè)函數(shù)的入?yún)?,那么在javascript中如何讓下一個(gè)異步操作等待上一個(gè)異步執(zhí)行得到結(jié)果之后再執(zhí)行呢?
我們現(xiàn)在已經(jīng)有了生成器并且知道next可以觸發(fā)生成器執(zhí)行到yield操作處,而且生成器會(huì)在遇到yield時(shí)立即執(zhí)行后面的語(yǔ)句并暫停,那么如果yield后面是一個(gè)異步操作,而異步操作獲取到結(jié)果之后再調(diào)用next不就實(shí)現(xiàn)了等待的效果么?
function asyncfuc(v) { setTimeout(function() { let r = v + 20; console.log(r); g.next(r); //把異步函數(shù)執(zhí)行得到的結(jié)果傳出并觸發(fā)下一個(gè)yield }, 500); } let g = function* gen() { let v1 = yield asyncfuc(0); let v2 = yield asyncfuc(v1); //上一個(gè)異步調(diào)用的結(jié)果作為下一個(gè)異步調(diào)用的入?yún)? return v2; }(); g.next();異步操作執(zhí)行鏈
有了前文的基礎(chǔ)我們可以實(shí)現(xiàn)一個(gè)用來(lái)執(zhí)行多個(gè)異步操作的函數(shù),定義一個(gè)run(...functions)方法依次執(zhí)行傳入的函數(shù),如下:
//這個(gè)方法用來(lái)模擬一個(gè)異步調(diào)用 function delay(time, func) { setTimeout(function() { func(`slept for ${time}`); }, time); } function run(...functions) { //構(gòu)造一個(gè)生成器循環(huán)執(zhí)行傳入的方法 let generator = function* sync(functions) { let result; for (var func of functions) { result = yield func(result, generator); //前一個(gè)方法執(zhí)行的結(jié)果作為下一個(gè)方法的入?yún)? } return result; }(functions); generator.next(); //觸發(fā)生成器立即執(zhí)行第一個(gè)方法 } //模擬異步方法調(diào)用, 斐波那契數(shù)列 function d(result, g) { delay(1000, (msg) => { let value = result; if (value) { [value.a, value.b] = [value.b, value.a + value.b]; } else { value = { a: 0, b: 1 }; } console.log(value.a); g.next(value); }); return result; } run(d, d, d); //順序執(zhí)行異步方法
以上實(shí)現(xiàn)有個(gè)值得注意的地方是業(yè)務(wù)處理函數(shù)必須帶上一個(gè)生成器作為參數(shù)并在獲得異步結(jié)果之后調(diào)用g.next(value),它影響了業(yè)務(wù)函數(shù)的api,因此具有比較大的侵入性。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86765.html
摘要:前言在異步編程之一中實(shí)現(xiàn)了一個(gè)異步函數(shù)調(diào)用鏈,它是一個(gè)順序調(diào)用鏈,很類似責(zé)任鏈模式,但現(xiàn)實(shí)往往不是平鋪直敘的,更多的其實(shí)是峰回路轉(zhuǎn),本文將繼續(xù)討論更多的用法。 前言 在《ES6 異步編程之一:Generator》中實(shí)現(xiàn)了一個(gè)異步函數(shù)調(diào)用鏈,它是一個(gè)順序調(diào)用鏈,很類似責(zé)任鏈模式,但現(xiàn)實(shí)往往不是平鋪直敘的,更多的其實(shí)是峰回路轉(zhuǎn),本文將繼續(xù)討論更多Generator的用法。 作為函數(shù)的Gen...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
摘要:執(zhí)行函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象,每一次函數(shù)里面的都相當(dāng)一次遍歷器對(duì)象的方法,并且可以通過(guò)方法傳入自定義的來(lái)改變函數(shù)的行為。函數(shù)可以通過(guò)配合函數(shù)更輕松更優(yōu)雅的實(shí)現(xiàn)異步編程和控制流管理。它和構(gòu)造函數(shù)的不同點(diǎn)類的內(nèi)部定義的所有方法,都是不可枚舉的。 let const的命令 在ES6之前,聲明變量只能用var,var方式聲明變量其實(shí)是很不合理的,準(zhǔn)確的說(shuō),是因?yàn)镋S5里面沒(méi)有塊級(jí)作用域是很不合...
摘要:回調(diào)函數(shù)這是異步編程最基本的方法。對(duì)象對(duì)象是工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口。誕生后,出現(xiàn)了函數(shù),它將異步編程帶入了一個(gè)全新的階段。 更多詳情點(diǎn)擊http://blog.zhangbing.club/Ja... Javascript 語(yǔ)言的執(zhí)行環(huán)境是單線程的,如果沒(méi)有異步編程,根本沒(méi)法用,非卡死不可。 為了解決這個(gè)問(wèn)題,Javascript語(yǔ)言將任務(wù)的執(zhí)行模式分成兩種...
摘要:傳統(tǒng)的異步方法回調(diào)函數(shù)事件監(jiān)聽(tīng)發(fā)布訂閱之前寫(xiě)過(guò)一篇關(guān)于的文章,里邊寫(xiě)過(guò)關(guān)于異步的一些概念。內(nèi)部函數(shù)就是的回調(diào)函數(shù),函數(shù)首先把函數(shù)的指針指向函數(shù)的下一步方法,如果沒(méi)有,就把函數(shù)傳給函數(shù)屬性,否則直接退出。 Generator函數(shù)與異步編程 因?yàn)閖s是單線程語(yǔ)言,所以需要異步編程的存在,要不效率太低會(huì)卡死。 傳統(tǒng)的異步方法 回調(diào)函數(shù) 事件監(jiān)聽(tīng) 發(fā)布/訂閱 Promise 之前寫(xiě)過(guò)一篇關(guān)...
閱讀 992·2021-11-23 09:51
閱讀 2705·2021-08-23 09:44
閱讀 668·2019-08-30 15:54
閱讀 1440·2019-08-30 13:53
閱讀 3116·2019-08-29 16:54
閱讀 2533·2019-08-29 16:26
閱讀 1200·2019-08-29 13:04
閱讀 2327·2019-08-26 13:50