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