js之yeild

来源:https://www.cnblogs.com/manshufeier/archive/2018/07/22/9349165.html
-Advertisement-
Play Games

1、萬惡的回調 對前端工程師來說,非同步回調是再熟悉不過了,瀏覽器中的各種交互邏輯都是通過事件回調實現的,前端邏輯越來越複雜,導致回調函數越來越多,同時 nodejs 的流行也讓 javascript 在後端的複雜場景中得到應用,在 nodejs 代碼中更是經常看到層層嵌套。非同步操作的回調一旦嵌套很多 ...


1、萬惡的回調

對前端工程師來說,非同步回調是再熟悉不過了,瀏覽器中的各種交互邏輯都是通過事件回調實現的,前端邏輯越來越複雜,導致回調函數越來越多,同時 nodejs 的流行也讓 javascript 在後端的複雜場景中得到應用,在 nodejs 代碼中更是經常看到層層嵌套。非同步操作的回調一旦嵌套很多,不僅代碼會變的臃腫,還很容易出錯。

以下是一個典型的非同步場景:先通過非同步請求獲取頁面數據,然後根據頁面數據請求用戶信息,最後根據用戶信息請求用戶的產品列表。過多的回調函數嵌套,使得程式難以維護,發展成萬惡的回調

$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

2、非同步流程式控制制

  • 最原始非同步流程的寫法,就是類似上面例子里的回調函數嵌套法,用過的人都知道,那叫一個酸爽。

  • 後來出現了 Promise ,它極大提高了代碼的可維護性,消除了萬惡的回調嵌套問題,並且現在已經成為 ES6 標準的一部分。

$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});
  • 之後在 nodejs 圈出現了 co 模塊,它基於 ES6 的 generator 和 yield ,讓我們能用同步的形式編寫非同步代碼
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});
  • 以上的 Promise 和 generator 最初創造它的本意都不是為瞭解決非同步流程式控制制。其中 Promise 是一種編程思想,用於“當xx數據準備完畢,then執行xx動作”這樣的場景,不只是非同步,同步代碼也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 創造性的拿來做非同步流程式控制制了。真正的非同步解決方案請大家期待 ES7 的 async 吧!本文以下主要介紹 co 模塊。

3、co 模塊

上文已經簡單介紹了co 模塊是能讓我們以同步的形式編寫非同步代碼的 nodejs 模塊,主要得益於 ES6 的 generator。nodejs >= 0.11 版本可以加 --harmony 參數來體驗 ES6 的 generator 特性,iojs 則已經預設開啟了 generator 的支持。

要瞭解 co ,就不得不先簡單瞭解下 ES6 的 generator 和 iterator。

  • Iterator

Iterator 迭代器是一個對象,知道如何從一個集合一次取出一項,而跟蹤它的當前序列所在的位置,

它的介面很簡單,一般擁有以下三個方法就可以了。

hasNext() //集合中是否還有下一個元素
next() //迭代到下一個元素
reset()//重置,我見到的代碼一般是拋出異常,即一般不支持多次迭代

它提供了一個next()方法返回序列中的下一個項目。

var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown
  • 可以沒有真正的集合(像Array),只要有相應的生成規則就行。這種情況下,沒有記憶體的限制,因此可以表示無限序列

  • 不調用next(),迭代器不進行迭代的,因此有延遲載入的特性。

  • 迭代器,本質上是一個狀態機,每個狀態下,進行一些操作,然後進入下一個狀態或維持狀態不變。

乍一看好像沒什麼奇特的,不就是一步步的取對象中的 key 和 value 嗎,for ... in也能做到,但是把它跟 generator 結合起來就大有用途了。

  • Generator

迭代器模式是很常用的設計模式,但是實現起來,很多東西是程式化的;當迭代規則比較複雜時,維護迭代器內的狀態,是比較麻煩的。 於是有了generator。Generator 生成器允許你通過寫一個可以保存自己狀態的的簡單函數來定義一個迭代演算法。 Generator 是一種可以停止併在之後重新進入的函數。生成器的環境(綁定的變數)會在每次執行後被保存,下次進入時可繼續使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用於生成一個迭代器對象,最大特點就是可以交出函數的執行權(即暫停執行)。只有在調用了 next() 函數之後,函數才會開始執行。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代碼定義了一個簡單的 generator,看起來就像一個普通的函數,區別是function關鍵字後面有個*號,函數體內可以使用yield語句進行流程式控制制

 var iter = gen();

 var a = iter.next();

 console.log(a); // {value:'hello', done:false}

 var b = iter.next();

 console.log(b); // {value:'world', done:false}

 var c = iter.next();

 console.log(c); // {value:true, done:true}

當執行gen()的時候,並不執行 generator 函數體,而是返回一個迭代器。迭代器具有next()方法,每次調用 next() 方法,函數就執行到yield語句的地方。next() 方法返回一個對象,其中value屬性表示 yield 關鍵詞後面表達式的值,done 屬性表示是否遍歷結束。generator 生成器通過nextyield的配合實現流程式控制制,上面的代碼執行了三次 next() ,generator 函數體才執行完畢。

  • co 模塊思路

從上面的例子可以看出,generator 函數體可以停在 yield 語句處,直到下一次執行 next()。co 模塊的思路就是利用 generator 的這個特性,將非同步操作跟在 yield 後面,當非同步操作完成並返回結果後,再觸發下一次 next() 。當然,跟在 yield 後面的非同步操作需要遵循一定的規範 thunks 和 promises。

4、7行代碼

再看看文章開頭的7行代碼:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}

首先生成一個迭代器,然後執行一遍 next(),得到的 value 是一個 Promise 對象,Promise.then() 裡面再執行 next()。當然這隻是一個原理性的演示,很多錯誤處理和迴圈調用 next() 的邏輯都沒有寫出來。

下麵做個簡單對比: 傳統方式,sayhello是一個非同步函數,執行helloworld會先輸出"world"再輸出"hello"

function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
function helloworld() {
    sayhello();
    console.log('world');
}
helloworld();

輸出

> "world"
> "hello"

co 的方式,會先輸出"hello"再輸出"world"

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}
function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
co(function *helloworld() {
    yield sayhello();
    console.log('world');
});

輸出

> "hello"
> "world"

5、消除回調金字塔

假設sayhello/sayworld/saybye是三個非同步函數,用真正的 co 模塊就可以這麼寫:

var co = require('co');
co(function *() {
    yield sayhello();
    yield sayworld();
    yield saybye();
});

輸出

> "hello"
> "world"
> "bye

 通過上面的分析,yield之後,實際上本次調用就結束了,控制權實際上已經轉到了外部調用了generator的next方法的函數,調用的過程中伴隨著狀態的改變。那麼如果外部函數不繼續調用next方法,那yield所在函數就相當於停在yield那裡了。所以把非同步的東西做完,要函數繼續執行,只要在合適的地方再次調用generator 的next就行,就好像函數在暫停後,繼續執行。

  • yield與return的區別:

(1)yield僅代表本次迭代完成,並且還必有下一次迭代;
(2) return則代表生成器函數完成;

利用生成器函數可以進行惰性求值,但無法獲取到第一次next函數傳入的值,而且只要執行了yield的返回操作,那麼構造函數一定沒有執行完成,除非遇到了顯式的return語句。

6、代理yeild

yield* 後面接受一個 iterable object 作為參數,然後去迭代(iterate)這個迭代器(iterable object),同時 yield* 本身這個表達式的值就是迭代器迭代完成時(done: true)的返回值。調用 generator function 會返回一個 generator object,這個對象本身也是一種 iterable object,所以,我們可以使用 yield* generator_function() 這種寫法。

➜ qiantou cat yield05.js

 function* outer(){

   yield 'begin';

 var ret = yield* inner();

 console.log(ret); yield 'end';

 }

 function * inner(){

 yield 'inner'; r

 return 'return from inner';

 }

 var it = outer(),v;

 v = it.next().value;

 console.log(v);

 v = it.next().value;

 console.log(v);

 v = it.next().value;

 console.log(v)

輸出:

 ➜ qiantou node yield05.js

 begin

 inner

 return from inner

 end

yield* 後面接受一個 iterable object 作為參數,然後去迭代(iterate)這個迭代器(iterable object),同時 yield* 本身這個表達式的值就是迭代器迭代完成時(done: true)的返回值。調用 generator function 會返回一個 generator object,這個對象本身也是一種 iterable object,所以,我們可以使用 yield* generator_function() 這種寫法。

  • yield* 的作用

(1)用原生語法,拋棄 co 的黑魔法,換取一點點點點性能提升

(2)明確表明自己的意圖,避免混淆

(3)調用時保證正確的 this 指向

7、總結

對於ES6的生成器函數總結有四點:
(1) yield必須放置在*函數中;
(2) 每次執行到yield時都會暫停函數中剩餘代碼的執行;
(3) *函數必須通過函數調用的方式(new方式會報錯)才能產生自身的實例,並且每個實例都互相獨立;
(4)一個生成器函數一旦迭代完成,則再也無法還原,一直停留在最後一個位置;

尤其是第二點,是非常強大的功能,暫停代碼執行,以前只有在瀏覽器環境中,alert、comfirm等系統內置函數才具有類似的能力,所以如果熟悉多線程的語言,你會找到類似的感覺,於是也有人說,有了yield,NodeJS就有協程的能力,完全可以處理多個需要協作的任務。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 字元串 字元串就是零個或多個排在一起的字元,放在單引號或雙引號之中。 單引號字元串的內部,可以使用雙引號。雙引號字元串的內部,可以使用單引號。 多行與轉義 如果要在單引號字元串的內部,使用單引號(或者在雙引號字元串的內部,使用雙引號),就必須在內部的單引號(或者雙引號)前面加上反斜杠,用來轉義。 字 ...
  • Javascript設計模式-工廠模式 我理解工廠模式,就是把相關的多個類提供一個統一入口的一個模式,讓你從一個入口就可以獲得多個類,提高工作效率. 但是對於工廠模式也會有三種類型的實現方式,分別是:簡單工廠模式,工廠方法模式和抽象工廠模式.它們分別是在各自基礎上有一定的改進. 簡單工廠模式 也被叫 ...
  • koa源碼閱讀[0] Node.js也是寫了兩三年的時間了,剛開始學習Node的時候,hello world就是創建一個HttpServer,後來在工作中也是經歷過Express、Koa1.x、Koa2.x以及最近還在研究的結合著TypeScript的routing-controllers(驅動依然 ...
  • 二 Viewport meta標簽: 語法:<meta name="viewport" content="name=value, name=value"> 參數: 1. width: 設置佈局viewport的特定值(" device-width" ) ...
  • 正因為如此,有不少同學使用 Sass 新的語法規則,而文件擴展名依舊使用的是“.sass”,這也就造成血案了,編譯時說編譯不出來。在此特別提醒新同學:“.sass”只能使用 Sass 老語法規則(縮進規則),“.scss”使用的是 Sass 的新語法規則,也就是 SCSS 語法規則(類似 CSS 語... ...
  • 主要需要註意的是不同方法他們本身返回的值應該是什麼,是數組當前的長度,還是取出的元素的值,再在splice函數裡面進行相應的return就可以了。具體如下: 用 splice函數實現 push方法 用 splice函數實現 pop方法 用 splice函數實現 shift方法 用 splice函數實 ...
  • 功能:停止事件冒泡 function stopBubble(e) { // 如果提供了事件對象,則這是一個非IE瀏覽器 if ( e && e.stopPropagation ) { // 因此它支持W3C的stopPropagation()方法 e.stopPropagation(); } els ...
  • 1、JavaScript對象的創建方式 在JavaScript中,創建對象的方式包括兩種:對象字面量和使用new表達式。對象字面量是一種靈活方便的書寫方式,例如: 1 2 3 4 5 6 var o1 = { p:”I’m in Object literal”, alertP:function(){ ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...