摘要:每個(gè)任務(wù)必須顯式地掛起自己,在任務(wù)切換發(fā)生時(shí)給予它完全的控制。在這些嘗試中,數(shù)據(jù)經(jīng)常在任務(wù)之間共享。但由于明確的暫停,幾乎沒(méi)有風(fēng)險(xiǎn)。
翻譯自
github
概述什么是generators?
我們可以把generators理解成一段可以暫停并重新開(kāi)始執(zhí)行的函數(shù)
function* genFunc() { // (A) console.log("First"); yield; //(B) console.log("Second"); //(C) }
function*是定義generator函數(shù)的關(guān)鍵字,yield是一個(gè)操作符,generator 可以通過(guò)yield暫停自己執(zhí)行,另外,generator可以通過(guò)yield接受輸入和對(duì)外輸入
當(dāng)我們調(diào)用genFunc(),我們得到一個(gè)generator對(duì)象genObj,我們可以通過(guò)這個(gè)genObj控制程序的執(zhí)行
const genObj = genFunc()
上面的程序初始會(huì)暫停在行A,調(diào)用genObj.next()會(huì)使程序繼續(xù)執(zhí)行直到遇到下一個(gè)yield
> genObj.next(); First { value: undefined, done: false }
這里先忽略genObj.next()返回的對(duì)象,之后會(huì)介紹
現(xiàn)在,程序暫停在了行B,再次調(diào)用 genObj.next(),程序又開(kāi)始執(zhí)行,行C被執(zhí)行
> genObj.next() Second { value: undefined, done: true }
然后,函數(shù)就執(zhí)行結(jié)束了,再次調(diào)用genObj.next()也不會(huì)有什么效果了
generator能扮演的角色
generators 可以扮演三種角色
迭代器(數(shù)據(jù)生產(chǎn)者)
每一個(gè)yield可以通過(guò)next()返回一個(gè)值,這意味著generators可以通過(guò)循環(huán)或遞歸生產(chǎn)一系列的值,因?yàn)間enerator對(duì)象實(shí)現(xiàn)了Iterable接口,generator生產(chǎn)的一系列值可以被ES6中任意支持可迭代對(duì)象的結(jié)構(gòu)處理,兩個(gè)例子,for of循環(huán)和擴(kuò)展操作(...)
觀察者(數(shù)據(jù)消費(fèi)者)
yield可以通過(guò)next()接受一個(gè)值,這意味著generator變成了一個(gè)暫停執(zhí)行的數(shù)據(jù)消費(fèi)者直到通過(guò)next()給generator傳遞了一個(gè)新值
協(xié)作程序(數(shù)據(jù)生產(chǎn)者和消費(fèi)者)
考慮到generators是可以暫停的并且可以同時(shí)作為數(shù)據(jù)生產(chǎn)者和消費(fèi)者,不會(huì)做太多的工作就可以把generator轉(zhuǎn)變成協(xié)作程序(合作進(jìn)行的多任務(wù))
下面詳細(xì)介紹這三種
generators作為數(shù)據(jù)生產(chǎn)者(iterators)generators同時(shí)實(shí)現(xiàn)了接口Iterable 和 Iterator(如下所示),這意味著,generator函數(shù)返回的對(duì)象是一個(gè)迭代器也是一個(gè)可迭代的對(duì)象
interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
generator對(duì)象完整的接口后面會(huì)提到,這里刪掉了接口Iterable的return()方法,因?yàn)檫@個(gè)方法這一小節(jié)用不到
generator函數(shù)通過(guò)yield生產(chǎn)一系列的值,這些值可以通過(guò)迭代器的next()方法來(lái)使用,例如下面的generator函數(shù)生成了值a和b
function* genFunc(){ yield "a" yield "b" }
交互展示如下
> const genObj = genFunc(); > genObj.next() { value: "a", done: false } > genObj.next() { value: "b", done: false } > genObj.next() // done: true => end of sequence { value: undefined, done: true }
迭代generator的三種方式
for of循環(huán)
for (const x of genFunc()) { console.log(x); } // Output: // a // b
擴(kuò)展操作符(...)
const arr = [...genFunc()]; // ["a", "b"]
解構(gòu)賦值
> const [x, y] = genFunc(); > x "a" > y "b"
generator中的return
上面的generator函數(shù)沒(méi)有包含一個(gè)顯式的return,一個(gè)隱式的return 返回undefined,讓我們?cè)囼?yàn)一個(gè)顯式返回return的generator
function* genFuncWithReturn() { yield "a"; yield "b"; return "result"; }
下面的結(jié)構(gòu)表明return 指定的值保存在最后一個(gè)next()返回的對(duì)象中
> const genObjWithReturn = genFuncWithReturn(); > genObjWithReturn.next() { value: "a", done: false } > genObjWithReturn.next() { value: "b", done: false } > genObjWithReturn.next() { value: "result", done: true }
然而,大部分和可迭代對(duì)象一起工作的結(jié)構(gòu)會(huì)忽略done屬性是true的對(duì)象的value值
for (const x of genFuncWithReturn()) { console.log(x); } // Output: // a // b const arr = [...genFuncWithReturn()]; // ["a", "b"]
yield*會(huì)考慮done屬性為true的value值,后面會(huì)介紹
generator函數(shù)中拋異常
如果一個(gè)異常離開(kāi)了generator函數(shù),next()可以拋出它
function* genFunc() { throw new Error("Problem!"); } const genObj = genFunc(); genObj.next(); // Error: Problem!
這意味著next()可以生產(chǎn)三種類型的值
對(duì)于可迭代序列中的一項(xiàng)x,它返回 {value:x,done:false}
對(duì)于可迭代序列的最后一項(xiàng),明確是return返回的z,它返回{value:z,done:true}
對(duì)于異常,它拋出這個(gè)異常
通過(guò) yield*遞歸
我們只能在generator函數(shù)中使用yield,如果我們想通過(guò)generator實(shí)現(xiàn)遞歸算法,我們就需要一種方式來(lái)在一個(gè)generator中調(diào)用另一個(gè)generator,這就用到了yield*,現(xiàn)在,我們只介紹yield*用在generator函數(shù)產(chǎn)生值的情況,之后介紹yield*用在generator接受值的情況
generator遞歸調(diào)用另一個(gè)generator的方式
function* foo() { yield "a"; yield "b"; } function* bar() { yield "x"; yield* foo(); yield "y"; }
執(zhí)行結(jié)構(gòu)
const arr = [...bar()]; //["x", "a", "b", "y"]
在內(nèi)部,yield*像下面這樣工作的
function* bar() { yield "x"; for (const value of foo()) { yield value; } yield "y"; }
另外,yield*的操作數(shù)不一定非得是一個(gè)generator函數(shù)生成的對(duì)象,可以是任何可迭代的
function* bla() { yield "sequence"; yield* ["of", "yielded"]; yield "values"; } const arr = [...bla()]; // ["sequence", "of", "yielded", "values"]
yield*考慮可迭代對(duì)象的最后一個(gè)值
ES6中的很多結(jié)構(gòu)會(huì)忽略generator函數(shù)返回的可迭代對(duì)象的最后一個(gè)值(例如 for of,擴(kuò)展操作符,如上面介紹過(guò)的那樣),但是,yield*的結(jié)果是這個(gè)值
function* genFuncWithReturn() { yield "a"; yield "b"; return "The result"; } function* logReturned(genObj) { const result = yield* genObj; console.log(result); // (A) }
執(zhí)行結(jié)果
> [...logReturned(genFuncWithReturn())] The result [ "a", "b" ]generators作為數(shù)據(jù)消費(fèi)者(observers)
作為數(shù)據(jù)的消費(fèi)者,generator函數(shù)返回的對(duì)象也實(shí)現(xiàn)了接口Observer
interface Observer { next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
作為observer,generator暫停執(zhí)行直到它接受到輸入值,這有三種類型的輸入,通過(guò)以下三種observer接口提供的方法
next() 發(fā)送正常的輸入
return() 終止generator
throw() 發(fā)送一個(gè)錯(cuò)誤
通過(guò)next()發(fā)送值
function* dataConsumer() { console.log("Started"); console.log(`1. ${yield}`); // (A) console.log(`2. ${yield}`); return "result"; }
首先得到generator對(duì)象
const genObj = dataConsumer();
然后執(zhí)行g(shù)enObj.next(),這會(huì)開(kāi)始這個(gè)generator.執(zhí)行到第一個(gè)yield處然后暫停。此時(shí)next()的結(jié)果是yield在行A產(chǎn)出的值(是undifined,因?yàn)檫@地方的yield后面沒(méi)有操作數(shù))
> genObj.next() //Started { value: undefined, done: false }
然后再調(diào)用next()兩次,第一次傳個(gè)參數(shù)"a",第二次傳參數(shù)"b"
> genObj.next("a") //1. a { value: undefined, done: false } > genObj.next("b") //2. b { value: "result", done: true }
可以看到,第一個(gè)next()調(diào)用的作用僅僅是開(kāi)始這個(gè)generator,只是為了后面的輸入做準(zhǔn)備
可以封裝一下
function coroutine(generatorFunction) { return function (...args) { const generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; }
使用
const wrapped = coroutine(function* () { console.log(`First input: ${yield}`); return "DONE"; }); > wrapped().next("hello!") First input: hello!
return() 和 throw()
generator對(duì)象有兩個(gè)另外的方法,return()和throw(),和next()類似
讓我們回顧一下next()是怎么工作的:
generator暫停在yield操作符
發(fā)送x給這個(gè)yield
繼續(xù)執(zhí)行到下一個(gè)yield,return或者throw:
yield x 導(dǎo)致 next() 返回 {value: x, done: false}
return x 導(dǎo)致 next() 返回 {value:x, done:true}
throw err 導(dǎo)致 next() 拋出err
return()和throw() 和next()類似工作,但在第二步有所不同
return(x) 在 yield的位置執(zhí)行 return x
throw(x) 在yield的位置執(zhí)行throw x
return()終止generator
return() 在 yield的位置執(zhí)行return
function* genFunc1() { try { console.log("Started"); yield; // (A) } finally { console.log("Exiting"); } } > const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.return("Result") Exiting { value: "Result", done: true }
阻止終止
我們可以阻止return()終止generator如果yield是在finally塊內(nèi)(或者在finally中使用return語(yǔ)句)
function* genFunc2() { try { console.log("Started"); yield; } finally { yield "Not done, yet!"; } }
這一次,return()沒(méi)有退出generator函數(shù),當(dāng)然,return()返回的對(duì)象的done屬性就是false
> const genObj2 = genFunc2(); > genObj2.next() Started { value: undefined, done: false } > genObj2.return("Result") { value: "Not done, yet!", done: false }
可以再執(zhí)行一次next()
> genObj2.next() { value: "Result", done: true }
發(fā)送一個(gè)錯(cuò)誤
throw()在yield的位置拋一個(gè)異常
function* genFunc1() { try { console.log("Started"); yield; // (A) } catch (error) { console.log("Caught: " + error); } }
> const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.throw(new Error("Problem!")) Caught: Error: Problem! { value: undefined, done: true }
yield* 完整的故事
到目前為止,我們只看到以yield的一個(gè)層面: 它傳播生成的值從被調(diào)用者到調(diào)用者。既然我們現(xiàn)在對(duì)generator接受值感興趣,我們就來(lái)看一下yield的另一個(gè)層面:yield*可以發(fā)送調(diào)用者接受的值給被調(diào)用者。在某種程度上,被調(diào)用者變成了活躍的generator,它可以被調(diào)用者生成的對(duì)象控制
function* callee() { console.log("callee: " + (yield)); } function* caller() { while (true) { yield* callee(); } }
> const callerObj = caller(); > callerObj.next() // start { value: undefined, done: false } > callerObj.next("a") callee: a { value: undefined, done: false } > callerObj.next("b") callee: b { value: undefined, done: false }generators作為協(xié)同程序(協(xié)作多個(gè)任務(wù))
這一節(jié)介紹generator完整的接口(組合作為數(shù)據(jù)生產(chǎn)者和消費(fèi)者兩種角色)和一個(gè)同時(shí)要使用這兩種角色的使用場(chǎng)景:協(xié)同操作多任務(wù)
完整的接口
interface Generator { next(value? : any) : IteratorResult; throw(value? : any) : IteratorResult; return(value? : any) : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
接口Generator結(jié)合了我們之前介紹過(guò)的兩個(gè)接口:輸出的Iterator和輸入的Observer
interface Iterator { // data producer next() : IteratorResult; return?(value? : any) : IteratorResult; } interface Observer { // data consumer next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
合作多任務(wù)
合作多任務(wù)是我們需要generators同時(shí)處理輸入和輸出,在介紹generator是如何工作的之前,讓我們先復(fù)習(xí)一下JavaScript當(dāng)前的并行狀態(tài)
js是單線程的,但有兩種方式可以消除這種限制
多進(jìn)程: Web Worker可以讓我們以多進(jìn)程的方式運(yùn)行js,對(duì)數(shù)據(jù)的共享訪問(wèn)是多進(jìn)程的最大缺陷之一,Web Worker避免這種缺陷通過(guò)不分享任何數(shù)據(jù)。也就是說(shuō),如果你想讓W(xué)eb Worker擁有一段數(shù)據(jù),要么發(fā)送給它一個(gè)數(shù)據(jù)的副本,要么把數(shù)據(jù)傳給它(這樣之后,你就不能再訪問(wèn)這些數(shù)據(jù)了)
合作多任務(wù):有不同的模式和庫(kù)可以嘗試進(jìn)行多任務(wù)處理,運(yùn)行多個(gè)任務(wù),但每次只執(zhí)行一個(gè)任務(wù)。每個(gè)任務(wù)必須顯式地掛起自己,在任務(wù)切換發(fā)生時(shí)給予它完全的控制。在這些嘗試中,數(shù)據(jù)經(jīng)常在任務(wù)之間共享。但由于明確的暫停,幾乎沒(méi)有風(fēng)險(xiǎn)。
通過(guò)generators來(lái)簡(jiǎn)化異步操作
一些基于Promise的庫(kù)通過(guò)generator來(lái)簡(jiǎn)化了異步代碼,generators作為Promise的客戶是非常理想的,因?yàn)樗鼈兛梢詴和V钡浇Y(jié)果返回
下面的例子表明co是如何工作的
co(function* () { try { const [croftStr, bondStr] = yield Promise.all([ // (A) getFile("http://localhost:8000/croft.json"), getFile("http://localhost:8000/bond.json"), ]); const croftJson = JSON.parse(croftStr); const bondJson = JSON.parse(bondStr); console.log(croftJson); console.log(bondJson); } catch (e) { console.log("Failure to read: " + e); } });
注意這段代碼看起來(lái)是多么的同步啊,雖然它在行A處執(zhí)行了一個(gè)異步調(diào)用。
使用generators對(duì)co的一個(gè)簡(jiǎn)單的實(shí)現(xiàn)
function co(genFunc) { const genObj = genFunc(); step(genObj.next()); function step({value,done}) { if (!done) { // A Promise was yielded value .then(result => { step(genObj.next(result)); // (A) }) .catch(error => { step(genObj.throw(error)); // (B) }); } } }
這里忽略了next()(行A)和throw()(行B)可以回拋異常
借助上面的使用分析一下:
首先得到generator對(duì)象
const genObj = genFunc();
然后將genObj.next()的返回值傳遞給step方法
step()中獲取到value和done,如果generator沒(méi)有執(zhí)行完,當(dāng)前的value就是上面使用中定義的promise
等到promise執(zhí)行完,然后將結(jié)果result傳遞給generator函數(shù)
genObj.next(result) 然后在generator中程序繼續(xù)往下執(zhí)行 const [croftStr, bondStr] = yield XXXX . . . .
注意行A處遞歸調(diào)用step(genObj.next(result)),使得generator函數(shù)中可以存在多個(gè)異步調(diào)用,而co都能處理
整個(gè)過(guò)程多么的巧妙啊。。。。。。。。。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90320.html
摘要:由于可以使用語(yǔ)句來(lái)暫停異步操作,這讓異步編程的代碼,很像同步數(shù)據(jù)流方法一樣。該臨時(shí)函數(shù)就叫做函數(shù)。下面就是簡(jiǎn)單的函數(shù)轉(zhuǎn)換器。 訪問(wèn)原文地址 對(duì)ES6的generators的介紹分為3個(gè)部分 第一部分base介紹及使用 第二部分基于generators和Promise實(shí)現(xiàn)最強(qiáng)大的異步處理邏輯 概述 Generator函數(shù)是協(xié)程在ES6的實(shí)現(xiàn),用來(lái)做異步流程的封裝,最大特點(diǎn)就是可以交出...
摘要:異步編程是每個(gè)使用編程的人都會(huì)遇到的問(wèn)題,無(wú)論是前端的請(qǐng)求,或是的各種異步。本文就來(lái)總結(jié)一下常見(jiàn)的四種處理異步編程的方法。利用一種鏈?zhǔn)秸{(diào)用的方法來(lái)組織異步代碼,可以將原來(lái)以回調(diào)函數(shù)形式調(diào)用的代碼改為鏈?zhǔn)秸{(diào)用。 異步編程是每個(gè)使用 JavaScript 編程的人都會(huì)遇到的問(wèn)題,無(wú)論是前端的 ajax 請(qǐng)求,或是 node 的各種異步 API。本文就來(lái)總結(jié)一下常見(jiàn)的四種處理異步編程的方法。...
摘要:同時(shí),迭代器有一個(gè)方法來(lái)向函數(shù)中暫停處拋出一個(gè)錯(cuò)誤,該錯(cuò)誤依然可以通過(guò)函數(shù)內(nèi)部的模塊進(jìn)行捕獲處理。 本文翻譯自:Diving Deeper With ES6 Generators 由于個(gè)人能力有限,翻譯中難免有紕漏和錯(cuò)誤,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...
摘要:更好的異步編程上面的方法可以適用于那些比較簡(jiǎn)單的異步工作流程。小結(jié)的組合目前是最強(qiáng)大,也是最優(yōu)雅的異步流程管理編程方式。 訪問(wèn)原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風(fēng)格,方便你把異步實(shí)現(xiàn)的那些細(xì)節(jié)藏在別處。這讓我們可以用一種很自然的方式書(shū)寫(xiě)我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風(fēng)格。 換句話說(shuō),通過(guò)將我們generato...
摘要:在函數(shù)定義上使用關(guān)鍵字來(lái)表示方法調(diào)用時(shí)返回的值。是一個(gè)有屬性的。這個(gè)指向一個(gè)函數(shù),這個(gè)函數(shù)返回關(guān)于這個(gè)對(duì)象的。在中所有的集合類對(duì)象和字符串都是,并且有自己默認(rèn)的。注意本身是不返回任何值的,它只向外部產(chǎn)生值。 ES6新特性 iterators and Generators ES6中引入了許多新特性,目前大量的JavaScript項(xiàng)目已經(jīng)使用了ES6來(lái)進(jìn)行開(kāi)發(fā),那么熟悉這些新的特性是十分必...
閱讀 2289·2019-08-30 15:56
閱讀 3121·2019-08-30 13:48
閱讀 1137·2019-08-30 10:52
閱讀 1508·2019-08-29 17:30
閱讀 433·2019-08-29 13:44
閱讀 3572·2019-08-29 12:53
閱讀 1129·2019-08-29 11:05
閱讀 2680·2019-08-26 13:24