欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript?Generator異步過度的實現詳解

 更新時間:2022年08月05日 16:56:48   作者:夏安  
生成器Generator是JavaScript?ES6引入的特性,它讓我們可以分段執(zhí)行一個函數。但是在談論生成器(Generator)之前,我們要先了解迭代器Iterator

異步過渡方案Generator

在使用 Generator 前,首先知道 Generator 是什么。

如果讀者有 Python 開發(fā)經驗,就會發(fā)現,無論是概念還是形式上,ES2015 中的 Generator 幾乎就是 Python 中 Generator 的翻版。

Generator 本質上是一個函數,它最大的特點就是可以被中斷,然后恢復執(zhí)行。通常來說,當開發(fā)者調用一個函數之后,這個函數的執(zhí)行就脫離了開發(fā)者的控制,只有函數執(zhí)行完畢之后,控制權才能重新回到調用者手中,因此程序員在編寫方法代碼時,唯一

能夠影響方法執(zhí)行的只有預先定義的 return 關鍵字。

Promise 也是如此,我們也無法控制 Promise 的執(zhí)行,新建一個 Promise 后,其狀態(tài)自動轉換為 pending,同時開始執(zhí)行,直到狀態(tài)改變后我們才能進行下一步操作。

Generator 函數不同,Generator 函數可以由用戶執(zhí)行中斷或者恢復執(zhí)行的操作,Generator 中斷后可以轉去執(zhí)行別的操作,然后再回過頭從中斷的地方恢復執(zhí)行。

1. Generator 的使用

Generator 函數和普通函數在外表上最大的區(qū)別有兩個:

  • function 關鍵字和方法名中間有個星號(*)。
  • 方法體中使用 yield 關鍵字。
function* Generator() {
  yield "Hello World";
  return "end";
}

和普通方法一樣,Generator 可以定義成多種形式:

// 普通方法形式
function* generator() {}
//函數表達式
const gen = function* generator() {}
// 對象的屬性方法
const obi = {
  * generator() {
  }
}

Generator 函數的狀態(tài)

yield 關鍵字用來定義函數執(zhí)行的狀態(tài),在前面代碼中,如果 Generator 中定義了 xyield 關鍵字,那么就有 x + 1 種狀態(tài)(+1是因為最后的 return 語句)。

2. Generator 函數的執(zhí)行

跟普通函數相比,Generator 函數更像是一個類或者一種數據類型,以下面的代碼為例,直接執(zhí)行一個 Generator 會得到一個 Generator 對象,而不是執(zhí)行方法體中的內容。

const gen = Generator();

按照通常的思路,gen 應該是 Generator() 函數的返回值,上面也提到Generator 函數可能有多種狀態(tài),讀者可能會因此聯(lián)想到 Promise,一個 Promise 也可能有三種狀態(tài)。不同的是 Promise 只能有一個確定的狀態(tài),而 Generator 對象會逐個經歷所有的狀態(tài),直到 Generator 函數執(zhí)行完畢。

當調用 Generator 函數之后,該函數并沒有立刻執(zhí)行,函數的返回結果也不是字符串,而是一個對象,可以將該對象理解為一個指針,指向 Generator 函數當前的狀態(tài)。(為了便于說明,我們下面采用指針的說法)。

Generator 被調用后,指針指向方法體的開始行,當 next 方法調用后,該指針向下移動,方法也跟著向下執(zhí)行,最后會停在第一個遇到的 yield 關鍵字前面,當再次調用 next 方法時,指針會繼續(xù)移動到下一個 yield 關鍵字,直到運行到方法的最后一行,以下面代碼為例,完整的執(zhí)行代碼如下:

function* Generator() {
  yield "Hello World";
  return "end";
}
const gen = Generator();
console.log(gen.next()); // { value: 'Hello World', done: false }
console.log(gen.next()); // { value: 'end', done: true }
console.log(gen.next()); // { value: undefined, done: true }

上面的代碼一共調用了三次 next 方法,每次都返回一個包含執(zhí)行信息的對象,包含一個表達式的值和一個標記執(zhí)行狀態(tài)的 flag。

第一次調用 next 方法,遇到一個 yield 語句后停止,返回對象的 value 的值就是 yield 語句的值,done 屬性用來標志 Generator 方法是否執(zhí)行完畢。

第二次調用 next 方法,程序執(zhí)行到 return 語句的位置,返回對象的 value 值即為 return 語句的值,如果沒有 return 語句,則會一直執(zhí)行到函數結束,value 值為 undefineddone 屬性值為 true。

第三次調用 next 方法時,Generator 已經執(zhí)行完畢,因此 value 的值為undefined。

2.1 yield 關鍵字

yield 本意為 生產 ,在 Python、Java 以及 C# 中都有 yield 關鍵字,但只有Python 中 yield 的語義相似(理由前面也說了)。

next 方法被調用時,Generator 函數開始向下執(zhí)行,遇到 yield 關鍵字時,會暫停當前操作,并且對 yield 后的表達式進行求值,無論 yield 后面表達式返回的是何種類型的值,yield 操作最后返回的都是一個對象,該對象有 valuedone 兩個屬性。

value 很好理解,如果后面是一個基本類型,那么 value 的值就是對應的值,更為常見的是 yield 后面跟的是 Promise 對象。

done 屬性表示當前 Generator 對象的狀態(tài),剛開始執(zhí)行時 done 屬性的值為false,當 Generator 執(zhí)行到最后一個 yield 或者 return 語句時,done 的值會變成 true,表示 Generator 執(zhí)行結束。

注意:yield關鍵字本身不產生返回值。例如下面的代碼:

function* foo(x) {
  const y = yield(x + 1);
  return y;
}
const gen = foo(5);
console.log(gen.next()); // { value: 6, done: false }
console.log(gen.next()); // { value: undefined, done: true }

為什么第二個 next 方法執(zhí)行后,y 的值卻是 undefined。

實際上,我們可以做如下理解:next 方法的返回值是 yield 關鍵字后面表達式的值,而 yield 關鍵字本身可以視為一個不產生返回值的函數,因此 y 并沒有被賦值。上面的例子中如果要計算 y 的值,可以將代碼改成:

function* foo(x) {
  let y;
  yield y = x + 1;
  return 'end';
}

next 方法還可以接受一個數值作為參數,代表上一個 yield 求值的結果。

function* foo(x) {
  const y = yield(x + 1);
  return y;
}
const gen = foo(5);
console.log(gen.next()); // { value: 6, done: false }
console.log(gen.next(10)); // { value: 10, done: true }

上面的代碼等價于:

function* foo(x) {
  let y = yield(x + 1);
  y = 10;
  return y;
}
const gen = foo(5);
console.log(gen.next()); // { value: 6, done: false }
console.log(gen.next()); // { value: 10, done: true }

next 可以接收參數代表可以從外部傳一個值到 Generator 函數內部,乍一看沒有什么用處,實際上正是這個特性使得 Generator 可以用來組織異步方法,我們會在后面介紹。

2.2 next 方法與 Iterator 接口

一個 Iterator 同樣使用 next 方法來遍歷元素。由于 Generator 函數會返回一個對象,而該對象實現了一個 Iterator 接口,因此所有能夠遍歷 Iterator 接口的方法都可以用來執(zhí)行 Generator,例如 for/ofaray.from()等。

可以使用 for/of 循環(huán)的方式來執(zhí)行 Generator 函數內的步驟,由于 for/of 本身就會調用 next 方法,因此不需要手動調用。

注意:循環(huán)會在 done 屬性為 true 時停止,以下面的代碼為例,最后的 'end' 并不會被打印出來,如果希望被打印,需要將最后的 return 改為 yield。

function* Generator() {
  yield "Hello Node";
  yield "From Lear"
  return "end"
}
const gen = Generator();
for (let i of gen) {
  console.log(i);
}
// 和 for/of 循環(huán)等價
console.log(Array.from(Generator()));;

前面提到過,直接打印 Generator 函數的示例沒有結果,但既然 Generator 函數返回了一個遍歷器,那么就應該具有 Symbol.iterator 屬性。

console.log(gen[Symbol.iterator]);

// 輸出:[Function: [Symbol.iterator]]

3. Generator 中的錯誤處理

Generator 函數的原型中定義了 throw 方法,用于拋出異常。

function* generator() {
  try {
    yield console.log("Hello");
  } catch (e) {
    console.log(e);
  }
  yield console.log("Node");
  return "end";
}
const gen = generator();
gen.next();
gen.throw("throw error");

// 輸出
// Hello
// throw error
// Node

上面代碼中,執(zhí)行完第一個 yield 操作后,Generator 對象拋出了異常,然后被函數體中 try/catch 捕獲。當異常被捕獲后,Generator 函數會繼續(xù)向下執(zhí)行,直到遇到下一個 yield 操作并輸出 yield 表達式的值。

function* generator() {
  try {
    yield console.log("Hello World");
  } catch (e) {
    console.log(e);
  }
  console.log('test');
  yield console.log("Node");
  return "end";
}
const gen = generator();
gen.next();
gen.throw("throw error");

// 輸出
// Hello World
// throw error
// test
// Node 

如果 Generator 函數在執(zhí)行的過程中出錯,也可以在外部進行捕獲。

function* generator() {
  yield console.log(undefined, undefined);
  return "end";
}
const gen = generator();
try {
  gen.next();
} catch (e) {
}

Generator 的原型對象還定義了 return() 方法,用來結束一個 Generator 函數的執(zhí)行,這和函數內部的 return 關鍵字不是一個概念。

function* generator() {
  yield console.log('Hello World');
  yield console.log('Hello 夏安');
  return "end";
}
const gen = generator();
gen.next(); // Hello World
gen.return();
// return() 方法后面的 next 不會被執(zhí)行
gen.next();

4. 用 Generator 組織異步方法

我們之所以可以使用 Generator 函數來處理異步任務,原因有二:

  • Generator 函數可以中斷和恢復執(zhí)行,這個特性由 yield 關鍵字來實現。
  • Generator 函數內外可以交換數據,這個特性由 next 函數來實現。

概括一下 Generator 函數處理異步操作的核心思想:先將函數暫停在某處,然后拿到異步操作的結果,然后再把這個結果傳到方法體內。

yield 關鍵字后面除了通常的函數表達式外,比較常見的是后面跟的是一個 Promise,由于 yield 關鍵字會對其后的表達式進行求值并返回,那么調用 next 方法時就會返回一個 Promise 對象,我們可以調用其 then 方法,并在回調中使用 next 方法將結果傳回 Generator。

function* gen() {
  const result = yield readFile_promise("foo.txt");
  console.log(result);
}
const g = gen();
const result = g.next();
result.value.then(function (data) {
  g.next(data);
});

上面的代碼中,Generator 函數封裝了 readFile_promise 方法,該方法返回一個 PromiseGenerator 函數對 readFile_promise 的調用方式和同步操作基本相同,除了 yield 關鍵字之外。

上面的 Generator 函數中只有一個異步操作,當有多個異步操作時,就會變成下面的形式。

function* gen() {
  const result = yield readFile_promise("foo.txt");
  console.log(result);
  const result2 = yield readFile_promise("bar.txt");
  console.log(result2);
}
const g = gen();
const result = g.next();
result.value.then(function (data) {
  g.next(data).value.then(function (data) {
    g.next(data);
  })
});

然而看起來還是嵌套的回調?難道使用 Generator 的初衷不是優(yōu)化嵌套寫法嗎?說的沒錯,雖然在調用時保持了同步形式,但我們需要手動執(zhí)行 Generator 函數,于是在執(zhí)行時又回到了嵌套調用。這是 Generator 的缺點。

5. Generator 的自動執(zhí)行

Generator 函數來說,我們也看到了要順序地讀取多個文件,就要像上面代碼那樣寫很多用來執(zhí)行的代碼。無論是 Promise 還是 Generator,就算在編寫異步代碼時能獲得便利,但執(zhí)行階段卻要寫更多的代碼,Promise 需要手動調用 then 方法,Generator 中則是手動調用 next 方法。

當需要順序執(zhí)行異步操作的個數比較少的情況下,開發(fā)者還可以接受手動執(zhí)行,但如果面對多個異步操作就有些難辦了,我們避免了回調地獄,卻又陷到了執(zhí)行地獄里面。我們不會是第一個遇到自動執(zhí)行問題的人,社區(qū)已經有了很多解決方案,但為了更深入地了解 PromiseGenerator,我們不妨先試著獨立地解決這個問題,如何能夠讓一個 Generator 函數自動執(zhí)行?

5.1 自動執(zhí)行器的實現

既然 Generator 函數是依靠 next 方法來執(zhí)行的,那么我們只要實現一個函數自動執(zhí)行 next 方法不就可以了嗎,針對這種思路,我們先試著寫出這樣的代碼:

function auto(generator) {
  const gen = generator();
  while (gen.next().value !== undefined) {
    gen.next();
  }
}

思路雖然沒錯,但這種寫法并不正確,首先這種方法只能用在最簡單的 Generator 函數上,例如下面這種:

function* generator() {
  yield 'Hello World';
  return 'end';
}

另一方面,由于 Generator 沒有 hasNext 方法,在 while 循環(huán)中作為條件的:gen.next().value !== undefined 在第一次條件判斷時就開始執(zhí)行了,這表示我們拿不到第一次執(zhí)行的結果。因此這種寫法行不通。

那么換一種思路,我們前面介紹了 for/of 循環(huán),那么也可以用它來執(zhí)行 Generator。

function* Generator() {
  yield "Hello World";
  yield "Hello 夏安";
  yield "end";
}
const gen = Generator();
for (let i of gen) {
  console.log(i);
}

// 輸出結果
// Hello World
// Hello 夏安
// end

看起來沒什么問題了,但同樣地也只能拿來執(zhí)行最簡單的 Generator 函數,然而我們的主要目的還是管理異步操作。

5.2 基于Promise的執(zhí)行器

前面實現的執(zhí)行器都是針對普通的 Generator 函數,即里面沒有包含異步操作,在實際應用中,yield 后面跟的大都是 Promise,這時候 for/of 實現的執(zhí)行器就不起作用了。

通過觀察,我們發(fā)現 Generator 的嵌套執(zhí)行是一種遞歸調用,每一次的嵌套的返回結果都是一個 Promise 對象。

const g = gen();
const result = g.next();
result.value.then(function (data) {
  g.next(data).value.then(function (data) {
    g.next(data);
  })
});

那么,我們可以根據這個寫出新的執(zhí)行函數。

function autoExec(gen) {
  function next(data) {
    const result = gen.next(data);
    // 判斷執(zhí)行是否結束
    if (result.done) return result.value;
    result.value.then(function (data) {
      next(data);
    });
  }
  next();
}

這個執(zhí)行器因為調用了 then 方法,因此只適用于 yield 后面跟一個 Promise 的方法。

5.3 使用 co 模塊來自動執(zhí)行

為了解決 generator 執(zhí)行的問題,TJ 于2013年6月發(fā)布了著名 co 模塊,這是一個用來自動執(zhí)行 Generator 函數的小工具,和 Generator 配合可以實現接近同步的調用方式,co 方法仍然會返回一個 Promise。

const co = require("co");
function* gen() {
  const result = yield readFilePromise("foo.txt");
  console.log(result);
  const result2 = yield readFilePromise("bar.txt");
  console.log(result2);
}
co(gen);

只要將 Generator 函數作為參數傳給 co 方法就能將內部的異步任務順序執(zhí)行,要使用 co 模塊,yield 后面的語句只能是 promsie 對象。

到此為止,我們對異步的處理有了一個比較妥當的方式,利用 generator+co,我們基本可以用同步的方式來書寫異步操作了。但 co 模塊仍有不足之處,由于它仍然返回一個 Promise,這代表如果想要獲得異步方法的返回值,還要寫成下面這種形式:

co(gen).then(function (value) {
  console.log(value);
});

另外,當面對多個異步操作時,除非將所有的異步操作都放在一個 Generator 函數中,否則如果需要對 co 的返回值進行進一步操作,仍然要將代碼寫到 Promise 的回調中去。

到此這篇關于JavaScript Generator異步過度的實現詳解的文章就介紹到這了,更多相關JavaScript Generator 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • js的隱含參數(arguments,callee,caller)使用方法

    js的隱含參數(arguments,callee,caller)使用方法

    本篇文章只要是對js的隱含參數(arguments,callee,caller)使用方法進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • javascript實現文本框標簽驗證的實例代碼

    javascript實現文本框標簽驗證的實例代碼

    這篇文章主要介紹了javascript實現文本框標簽驗證的實例代碼,需要的朋友可以參考下
    2018-10-10
  • js中arguments對象的深入理解

    js中arguments對象的深入理解

    這篇文章主要給大家介紹了關于js中arguments對象的深入理解,文中通過示例代碼介紹的非常詳細,對大家學習或者使用javascript具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-05-05
  • Blazor實現微信的Tab切換功能

    Blazor實現微信的Tab切換功能

    這篇文章主要介紹了Blazor實現微信的Tab切換功能,本文中的UI組件使用的是MASA?Blazor,您也可以是其他的UI框架,這個并不影響實際的運行效果,本文案例是兼容PC和Android的,演示效果是android中執(zhí)行的,在PC中執(zhí)行效果依然有效,需要的朋友可以參考下
    2023-01-01
  • FF下zoom的替代方案 單位em

    FF下zoom的替代方案 單位em

    css 屬性zoom是ie私有屬性 在FF中(或者說僅實現CSS標準的其他瀏覽器中)無法使用,一直在網上搜索它的替代方案,但沒有收獲,后來聽群里有朋友說em或可解決此問題,經過研究和測試,發(fā)現果然可以解決。
    2008-08-08
  • JavaScript?評測代碼運行速度的案例代碼

    JavaScript?評測代碼運行速度的案例代碼

    在?JavaScript?中,可以使用?performance.now()?API?來評測代碼的運行速度。該?API?返回當前頁面的高精度時間戳,您可以在代碼執(zhí)行前后調用它來計算代碼執(zhí)行所需的時間,這篇文章主要介紹了JavaScript?評測代碼運行速度,需要的朋友可以參考下
    2023-02-02
  • JS中call(),apply(),bind()函數的區(qū)別與用法詳解

    JS中call(),apply(),bind()函數的區(qū)別與用法詳解

    這篇文章主要介紹了JS中call(),apply(),bind()函數的高級用法詳解,需要的朋友可以參考下
    2022-12-12
  • 基于JS實現的隨機數字抽簽實例

    基于JS實現的隨機數字抽簽實例

    本文分享了基于JS實現的隨機數字抽簽的實例代碼。小編認為具很好的參考價值,感興趣的朋友可以看下
    2016-12-12
  • JavaScript對象反射用法實例

    JavaScript對象反射用法實例

    這篇文章主要介紹了JavaScript對象反射用法,實例分析了反射DOM對象和自定義對象的具體用法,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-04-04
  • 一文剖析JavaScript中閉包的難點

    一文剖析JavaScript中閉包的難點

    這篇文章主要為大家詳細介紹了JavaScript中閉包的一些難點,文中的示例代碼講解詳細,對我們學習JavaScript有一定幫助,需要的可以參考一下
    2022-09-09

最新評論