摘要:中的的引入,極大程度上改變了程序員對(duì)迭代器的看法,并為解決提供了新方法。被稱為,也有些人把的返回值稱為一個(gè)。其中屬性包含實(shí)際返回的數(shù)值,屬性為布爾值,標(biāo)記迭代器是否完成迭代。
原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/JavaScript_Generator.html
源代碼: https://github.com/RobinQu/Programing-In-JavaScript/blob/master/chapters/JavaScript_Core/Functional_JavaScript/JavasSript_Generator.md
本文需要補(bǔ)充更多例子
本文存在批注,但該網(wǎng)站的Markdown編輯器不支持,所以無(wú)法正常展示,請(qǐng)到原文參考。
Javascript GeneratorES6中的Generator的引入,極大程度上改變了Javascript程序員對(duì)迭代器的看法,并為解決callback hell1提供了新方法。
Generator是一個(gè)與語(yǔ)言無(wú)關(guān)的特性,理論上它應(yīng)該存在于所有Javascript引擎內(nèi),但是目前真正完整實(shí)現(xiàn)的,只有在node --harmony 下。所以后文所有的解釋,都以node環(huán)境舉例,需要的啟動(dòng)參數(shù)為node --harmony --use_strict。
V8中所實(shí)現(xiàn)的Generator和標(biāo)準(zhǔn)之中說(shuō)的又有區(qū)別,這個(gè)可以參考一下MDC的相關(guān)文檔2。而且,V8在寫作這篇文章時(shí),并沒(méi)有實(shí)現(xiàn)Iterator。
用作迭代器我們以一個(gè)簡(jiǎn)單的例子3開始:
function* argumentsGenerator() { for (let i = 0; i < arguments.length; i += 1) { yield arguments[i]; } }
我們希望迭代傳入的每個(gè)實(shí)參:
var argumentsIterator = argumentsGenerator("a", "b", "c"); // Prints "a b c" console.log( argumentsIterator.next().value, argumentsIterator.next().value, argumentsIterator.next().value );
我們可以簡(jiǎn)單的理解:
Generator其實(shí)是生成Iterator的方法。argumentsGenerator被稱為GeneartorFunction,也有些人把GeneartorFunction的返回值稱為一個(gè)Geneartor。
yield可以中斷GeneartorFunction的運(yùn)行;而在下一次yield時(shí),可以恢復(fù)運(yùn)行。
返回的Iterator上,有next成員方法,能夠返回迭代值。其中value屬性包含實(shí)際返回的數(shù)值,done屬性為布爾值,標(biāo)記迭代器是否完成迭代。要注意的是,在done屬性為true后繼續(xù)運(yùn)行next方法會(huì)產(chǎn)生異常。
完整的ES實(shí)現(xiàn)中,for-of循環(huán)正是為了快速迭代一個(gè)iterator的:
// Prints "a", "b", "c" for(let value of argumentsIterator) { console.log(value); }
可惜,目前版本的node不支持for-of。
說(shuō)到這里,大多數(shù)有經(jīng)驗(yàn)的Javascript程序員會(huì)表示不屑,因?yàn)檫@些都可以通過(guò)自己編寫一個(gè)函數(shù)來(lái)實(shí)現(xiàn)。我們?cè)賮?lái)看一個(gè)例子:
function* fibonacci() { let a = 0, b = 1; //1, 2 while(true) { yield a; a = b; b = a + b; } } for(let value of fibonacci()) { console.log(value); }
fibonacci序列是無(wú)窮的數(shù)字序列,你可以用函數(shù)的迭代來(lái)生成,但是遠(yuǎn)沒(méi)有用Generator來(lái)的簡(jiǎn)潔。
再來(lái)個(gè)更有趣的。我們可以利用yield*語(yǔ)法,將yield操作代理到另外一個(gè)Generator。
let delegatedIterator = (function* () { yield "Hello!"; yield "Bye!"; }()); let delegatingIterator = (function* () { yield "Greetings!"; yield* delegatedIterator; yield "Ok, bye."; }()); // Prints "Greetings!", "Hello!", "Bye!", "Ok, bye." for(let value of delegatingIterator) { console.log(value); }用作流程控制
yield可以暫停運(yùn)行流程,那么便為改變執(zhí)行流程提供了可能4。這和Python的coroutine類似。
co已經(jīng)將此特性封裝的非常完美了。我們?cè)谶@里簡(jiǎn)單的討論其實(shí)現(xiàn)。
The classic example of this is consumer-producer relationships: generators that produce values, and then consumers that use them. The two generators are said to be symmetric – a continuous evaluation where coroutines yield to each other, rather than two functions that call each other.
Geneartor之所以可用來(lái)控制代碼流程,就是通過(guò)yield來(lái)將兩個(gè)或者多個(gè)Geneartor的執(zhí)行路徑互相切換。這種切換是語(yǔ)句級(jí)別的,而不是函數(shù)調(diào)用級(jí)別的。其本質(zhì)是CPS變幻,后文會(huì)給出解釋。
這里要補(bǔ)充yield的若干行為:
next方法接受一個(gè)參數(shù),傳入的參數(shù)是yield表達(dá)式的返回值;即yield既可以產(chǎn)生數(shù)值,也可以接受數(shù)值
throw方法會(huì)拋出一個(gè)異常,并終止迭代
GeneratorFunction的return語(yǔ)句等同于一個(gè)yield
將異步“變”為同步假設(shè)我們希望有如下語(yǔ)法風(fēng)格:
suspend傳入一個(gè)GeneratorFunction
suspend返回一個(gè)簡(jiǎn)單的函數(shù),接受一個(gè)node風(fēng)格的回調(diào)函數(shù)
所有的異步調(diào)用都通過(guò)yield,看起來(lái)像同步調(diào)用
給定一個(gè)特殊的回調(diào),讓保證異步調(diào)用的返回值作為yield的返回值,并且讓腳本繼續(xù)
GeneratorFunction的返回值和執(zhí)行過(guò)程的錯(cuò)誤都會(huì)會(huì)傳入全局的回調(diào)函數(shù)
更具體的,如下例子:
var fs = require("fs"); suspend(function*(resume) { var content = yield fs.readFile(__filename, resume); var list = yield fs.readdir(__dirname, resume); return [content, list]; })(function(e, res) { console.log(e,res); });
上面分別進(jìn)行了一個(gè)讀文件和列目錄的操作,均是異步操作。為了實(shí)現(xiàn)這樣的suspend和resume。我們簡(jiǎn)單的封裝Generator的API:
var slice = Array.prototype.slice.call.bind(Array.prototype.slice); var suspend = function(gen) {//`gen` is a generator function return function(callback) { var args, iterator, next, ctx, done; ctx = this; args = slice(arguments); next = function(e) { if(e) {//throw up or send to callback return callback ? callback(e) : iterator.throw(e); } var ret = iterator.next(slice(arguments, 1)); if(ret.done && callback) {//run callback is needed callback(null, ret.value); } }; resume = function(e) { next.apply(ctx, arguments); }; args.unshift(resume); iterator = gen.apply(this, args); next();//kickoff }; };有容乃大
目前我們只支持回調(diào)形勢(shì)的API,并且需要顯示的傳入resume作為API的回調(diào)。為了像co那樣支持更多的可以作為yield參數(shù)。co中,作者將所有形勢(shì)的異步對(duì)象都?xì)w結(jié)為一種名為thunk的回調(diào)形式。
那什么是thunk呢?thunk就是支持標(biāo)準(zhǔn)的node風(fēng)格回調(diào)的一個(gè)函數(shù): fn(callback)。
首先我們將suspend修改為自動(dòng)resume:
var slice = Array.prototype.slice.call.bind(Array.prototype.slice); var suspend = function(gen) { return function(callback) { var args, iterator, next, ctx, done; ctx = this; args = slice(arguments); next = function(e) { if(e) { return callback ? callback(e) : iterator.throw(e); } var ret = iterator.next(slice(arguments, 1)); if(ret.done && callback) { return callback(null, ret.value); } if("function" === typeof ret.value) {//shold yield a thunk ret.value.call(ctx, function() {//resume function next.apply(ctx, arguments); }); } }; iterator = gen.apply(this, args); next(); }; };
注意,這個(gè)時(shí)候,我們只能yield一個(gè)thunk,我們的使用方法也要發(fā)生改變:
var fs = require("fs"); read = function(filename) {//wrap native API to a thunk return function(callback) { fs.readFile(filename, callback); }; }; suspend(function*() {//return value of this generator function is passed to callback return yield read(__filename); })(function(e, res) { console.log(e,res); });
接下來(lái),我們要讓這個(gè)suspend更加有用,我們可以支持如下內(nèi)容穿入到y(tǒng)ield
GeneratorFunction
Generator
Thunk
var slice = Array.prototype.slice.call.bind(Array.prototype.slice); var isGeneratorFunction = function(obj) { return obj && obj.constructor && "GeneratorFunction" == obj.constructor.name; }; var isGenerator = function(obj) { return obj && "function" == typeof obj.next && "function" == typeof obj.throw; }; var suspend = function(gen) { return function(callback) { var args, iterator, next, ctx, done, thunk; ctx = this; args = slice(arguments); next = function(e) { if(e) { return callback ? callback(e) : iterator.throw(e); } var ret = iterator.next(slice(arguments, 1)); if(ret.done && callback) { return callback(null, ret.value); } if(isGeneratorFunction(ret.value)) {//check if it"s a generator thunk = suspend(ret.value); } else if("function" === typeof ret.value) {//shold yield a thunk thunk = ret.value; } else if(isGenerator(ret.value)) { thunk = suspend(ret.value); } thunk.call(ctx, function() {//resume function next.apply(ctx, arguments); }); }; if(isGeneratorFunction(gen)) { iterator = gen.apply(this, args); } else {//assume it"s a iterator iterator = gen; } next(); }; };
在使用時(shí),我們可以傳入三種對(duì)象到y(tǒng)ield:
var fs = require("fs"); read = function(filename) { return function(callback) { fs.readFile(filename, callback); }; }; var read1 = function*() { return yield read(__filename); }; var read2 = function*() { return yield read(__filename); }; suspend(function*() { var one = yield read1; var two = yield read2(); var three = yield read(__filename); return [one, two, three]; })(function(e, res) { console.log(e,res); });
當(dāng)然,到這里,大家應(yīng)該都明白如何讓suspend兼容更多的數(shù)據(jù)類型,例如Promise、數(shù)組等。但更多的擴(kuò)展,在這里就不再贅述。這里的suspend可以就說(shuō)就是精簡(jiǎn)的co了。
yield的引入,讓流程控制走上了一條康莊大道,不需要使用復(fù)雜的Promise、也不用使用難看的async。同時(shí),從性能角度,yield可以通過(guò)V8的后續(xù)優(yōu)化,性能進(jìn)一步提升,目前來(lái)說(shuō)yield的性能并不差5。
yield的轉(zhuǎn)換yield的本質(zhì)是一個(gè)語(yǔ)法糖,底層的實(shí)現(xiàn)方式便是CPS變換6。也就是說(shuō)yield是可以用循環(huán)和遞歸重新實(shí)現(xiàn)的,根本用不著一定在V8層面實(shí)現(xiàn)。但筆者認(rèn)為,純Javascript實(shí)現(xiàn)的"yield"會(huì)造成大量的堆棧消耗,在性能上毫無(wú)優(yōu)勢(shì)可言。從性能上考慮,V8可以優(yōu)化yield的編譯,實(shí)現(xiàn)更高性能的轉(zhuǎn)換。
關(guān)于CPS變換的細(xì)節(jié),會(huì)在之后的文章中詳細(xì)解說(shuō)。
http://callbackhell.com/??
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators??
https://github.com/JustinDrake/node-es6-examples#generators??
http://dailyjs.com/2013/05/31/suspend/??
http://dailyjs.com/2013/10/17/yield/??
http://en.wikipedia.org/wiki/Continuation-passing_style??
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78131.html
摘要:不少第三方模塊并沒(méi)有做到異步調(diào)用,卻裝作支持回調(diào),堆棧的風(fēng)險(xiǎn)就更大。我們可以編寫一個(gè)高階函數(shù),讓傳入的函數(shù)順序執(zhí)行還是我們之前的例子看起來(lái)還是很不錯(cuò)的,簡(jiǎn)潔并且清晰,最終的代碼量也沒(méi)有增加。 原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/Async_Programing_In_JavaScript....
摘要:異步編程解決方案筆記最近讀了樸靈老師的深入淺出中異步編程一章,并參考了一些有趣的文章。另外回調(diào)函數(shù)中的也失去了意義,這會(huì)使我們的程序必須依賴于副作用。 JavaScript 異步編程解決方案筆記 最近讀了樸靈老師的《深入淺出NodeJS》中《異步編程》一章,并參考了一些有趣的文章。在此做個(gè)筆記,記錄并鞏固學(xué)到的知識(shí)。 JavaScript異步編程的兩個(gè)核心難點(diǎn) 異步I/O、事件驅(qū)動(dòng)使得...
摘要:學(xué)習(xí)開發(fā),無(wú)論是前端開發(fā)還是都避免不了要接觸異步編程這個(gè)問(wèn)題就和其它大多數(shù)以多線程同步為主的編程語(yǔ)言不同的主要設(shè)計(jì)是單線程異步模型。由于異步編程可以實(shí)現(xiàn)非阻塞的調(diào)用效果,引入異步編程自然就是順理成章的事情了。 學(xué)習(xí)js開發(fā),無(wú)論是前端開發(fā)還是node.js,都避免不了要接觸異步編程這個(gè)問(wèn)題,就和其它大多數(shù)以多線程同步為主的編程語(yǔ)言不同,js的主要設(shè)計(jì)是單線程異步模型。正因?yàn)閖s天生的與...
摘要:從最開始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
摘要:序在中,大家討論的最多的就是異步編程的操作,如何避免回調(diào)的多次嵌套。今天所講的和就是和異步編程有關(guān),可以幫助我們把異步編程同步化。然而這樣的方法依然需要依賴外在的庫(kù)函數(shù),于是中提出了和關(guān)鍵字。 序 在Javascript中,大家討論的最多的就是異步編程的操作,如何避免回調(diào)的多次嵌套。異步操作的回調(diào)一旦嵌套很多,不僅代碼會(huì)變的臃腫,還很容易出錯(cuò)。各種各樣的異步編程解決方案也被不斷提出,例...
閱讀 2154·2023-04-26 00:23
閱讀 828·2021-09-08 09:45
閱讀 2448·2019-08-28 18:20
閱讀 2555·2019-08-26 13:51
閱讀 1608·2019-08-26 10:32
閱讀 1405·2019-08-26 10:24
閱讀 2042·2019-08-26 10:23
閱讀 2209·2019-08-23 18:10