摘要:中的的引入,極大程度上改變了程序員對迭代器的看法,并為解決提供了新方法。被稱為,也有些人把的返回值稱為一個。其中屬性包含實際返回的數值,屬性為布爾值,標記迭代器是否完成迭代。
原文: 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
本文需要補充更多例子
本文存在批注,但該網站的Markdown編輯器不支持,所以無法正常展示,請到原文參考。
Javascript GeneratorES6中的Generator的引入,極大程度上改變了Javascript程序員對迭代器的看法,并為解決callback hell1提供了新方法。
Generator是一個與語言無關的特性,理論上它應該存在于所有Javascript引擎內,但是目前真正完整實現的,只有在node --harmony 下。所以后文所有的解釋,都以node環境舉例,需要的啟動參數為node --harmony --use_strict。
V8中所實現的Generator和標準之中說的又有區別,這個可以參考一下MDC的相關文檔2。而且,V8在寫作這篇文章時,并沒有實現Iterator。
用作迭代器我們以一個簡單的例子3開始:
function* argumentsGenerator() { for (let i = 0; i < arguments.length; i += 1) { yield arguments[i]; } }
我們希望迭代傳入的每個實參:
var argumentsIterator = argumentsGenerator("a", "b", "c"); // Prints "a b c" console.log( argumentsIterator.next().value, argumentsIterator.next().value, argumentsIterator.next().value );
我們可以簡單的理解:
Generator其實是生成Iterator的方法。argumentsGenerator被稱為GeneartorFunction,也有些人把GeneartorFunction的返回值稱為一個Geneartor。
yield可以中斷GeneartorFunction的運行;而在下一次yield時,可以恢復運行。
返回的Iterator上,有next成員方法,能夠返回迭代值。其中value屬性包含實際返回的數值,done屬性為布爾值,標記迭代器是否完成迭代。要注意的是,在done屬性為true后繼續運行next方法會產生異常。
完整的ES實現中,for-of循環正是為了快速迭代一個iterator的:
// Prints "a", "b", "c" for(let value of argumentsIterator) { console.log(value); }
可惜,目前版本的node不支持for-of。
說到這里,大多數有經驗的Javascript程序員會表示不屑,因為這些都可以通過自己編寫一個函數來實現。我們再來看一個例子:
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序列是無窮的數字序列,你可以用函數的迭代來生成,但是遠沒有用Generator來的簡潔。
再來個更有趣的。我們可以利用yield*語法,將yield操作代理到另外一個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可以暫停運行流程,那么便為改變執行流程提供了可能4。這和Python的coroutine類似。
co已經將此特性封裝的非常完美了。我們在這里簡單的討論其實現。
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之所以可用來控制代碼流程,就是通過yield來將兩個或者多個Geneartor的執行路徑互相切換。這種切換是語句級別的,而不是函數調用級別的。其本質是CPS變幻,后文會給出解釋。
這里要補充yield的若干行為:
next方法接受一個參數,傳入的參數是yield表達式的返回值;即yield既可以產生數值,也可以接受數值
throw方法會拋出一個異常,并終止迭代
GeneratorFunction的return語句等同于一個yield
將異步“變”為同步假設我們希望有如下語法風格:
suspend傳入一個GeneratorFunction
suspend返回一個簡單的函數,接受一個node風格的回調函數
所有的異步調用都通過yield,看起來像同步調用
給定一個特殊的回調,讓保證異步調用的返回值作為yield的返回值,并且讓腳本繼續
GeneratorFunction的返回值和執行過程的錯誤都會會傳入全局的回調函數
更具體的,如下例子:
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); });
上面分別進行了一個讀文件和列目錄的操作,均是異步操作。為了實現這樣的suspend和resume。我們簡單的封裝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 }; };有容乃大
目前我們只支持回調形勢的API,并且需要顯示的傳入resume作為API的回調。為了像co那樣支持更多的可以作為yield參數。co中,作者將所有形勢的異步對象都歸結為一種名為thunk的回調形式。
那什么是thunk呢?thunk就是支持標準的node風格回調的一個函數: fn(callback)。
首先我們將suspend修改為自動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(); }; };
注意,這個時候,我們只能yield一個thunk,我們的使用方法也要發生改變:
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); });
接下來,我們要讓這個suspend更加有用,我們可以支持如下內容穿入到yield
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(); }; };
在使用時,我們可以傳入三種對象到yield:
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); });
當然,到這里,大家應該都明白如何讓suspend兼容更多的數據類型,例如Promise、數組等。但更多的擴展,在這里就不再贅述。這里的suspend可以就說就是精簡的co了。
yield的引入,讓流程控制走上了一條康莊大道,不需要使用復雜的Promise、也不用使用難看的async。同時,從性能角度,yield可以通過V8的后續優化,性能進一步提升,目前來說yield的性能并不差5。
yield的轉換yield的本質是一個語法糖,底層的實現方式便是CPS變換6。也就是說yield是可以用循環和遞歸重新實現的,根本用不著一定在V8層面實現。但筆者認為,純Javascript實現的"yield"會造成大量的堆棧消耗,在性能上毫無優勢可言。從性能上考慮,V8可以優化yield的編譯,實現更高性能的轉換。
關于CPS變換的細節,會在之后的文章中詳細解說。
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??
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78131.html
摘要:不少第三方模塊并沒有做到異步調用,卻裝作支持回調,堆棧的風險就更大。我們可以編寫一個高階函數,讓傳入的函數順序執行還是我們之前的例子看起來還是很不錯的,簡潔并且清晰,最終的代碼量也沒有增加。 原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/Async_Programing_In_JavaScript....
摘要:異步編程解決方案筆記最近讀了樸靈老師的深入淺出中異步編程一章,并參考了一些有趣的文章。另外回調函數中的也失去了意義,這會使我們的程序必須依賴于副作用。 JavaScript 異步編程解決方案筆記 最近讀了樸靈老師的《深入淺出NodeJS》中《異步編程》一章,并參考了一些有趣的文章。在此做個筆記,記錄并鞏固學到的知識。 JavaScript異步編程的兩個核心難點 異步I/O、事件驅動使得...
摘要:學習開發,無論是前端開發還是都避免不了要接觸異步編程這個問題就和其它大多數以多線程同步為主的編程語言不同的主要設計是單線程異步模型。由于異步編程可以實現非阻塞的調用效果,引入異步編程自然就是順理成章的事情了。 學習js開發,無論是前端開發還是node.js,都避免不了要接觸異步編程這個問題,就和其它大多數以多線程同步為主的編程語言不同,js的主要設計是單線程異步模型。正因為js天生的與...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:序在中,大家討論的最多的就是異步編程的操作,如何避免回調的多次嵌套。今天所講的和就是和異步編程有關,可以幫助我們把異步編程同步化。然而這樣的方法依然需要依賴外在的庫函數,于是中提出了和關鍵字。 序 在Javascript中,大家討論的最多的就是異步編程的操作,如何避免回調的多次嵌套。異步操作的回調一旦嵌套很多,不僅代碼會變的臃腫,還很容易出錯。各種各樣的異步編程解決方案也被不斷提出,例...
閱讀 2137·2023-04-26 00:23
閱讀 807·2021-09-08 09:45
閱讀 2435·2019-08-28 18:20
閱讀 2542·2019-08-26 13:51
閱讀 1595·2019-08-26 10:32
閱讀 1392·2019-08-26 10:24
閱讀 2027·2019-08-26 10:23
閱讀 2196·2019-08-23 18:10