自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端人不了解的迭代器和生成器

開發(fā) 前端
迭代器只需要知道集合中的當(dāng)前位置,而其他循環(huán)則需要預(yù)先加載整個(gè)集合才能循環(huán)遍歷它。迭代器使用 next() 方法訪問集合中的下一個(gè)元素。但是,為了使用迭代器,值或數(shù)據(jù)結(jié)構(gòu)應(yīng)該是可迭代的。數(shù)組、字符串、映射、集合是 JavaScript 中的可迭代對(duì)象。普通對(duì)象是不可迭代的。

迭代器和生成器是 ES6 中引入的特性。迭代器通過一次消費(fèi)一個(gè)項(xiàng)目列表來提高效率,類似于數(shù)據(jù)流。生成器是一種能夠暫停執(zhí)行的特殊函數(shù)。調(diào)用生成器允許以塊的形式(一次一個(gè))生成數(shù)據(jù),而無需先將其存儲(chǔ)在列表中。下面就來深入理解 JavaScript 中的迭代器和生成器,看看它們是如何使用的,又有何妙用!

迭代器

JavaScript 中的迭代器可以分別兩種:同步迭代器和異步迭代器。

1. 同步迭代器

(1)迭代器和可迭代對(duì)象

在 JavaScript 中有很多方法可以遍歷數(shù)據(jù)結(jié)構(gòu)。例如,使用 for 循環(huán)或使用 while 循環(huán)。迭代器具有類似的功能,但有顯著差異。

迭代器只需要知道集合中的當(dāng)前位置,而其他循環(huán)則需要預(yù)先加載整個(gè)集合才能循環(huán)遍歷它。迭代器使用 next() 方法訪問集合中的下一個(gè)元素。但是,為了使用迭代器,值或數(shù)據(jù)結(jié)構(gòu)應(yīng)該是可迭代的。數(shù)組、字符串、映射、集合是 JavaScript 中的可迭代對(duì)象。普通對(duì)象是不可迭代的。

(2)定義迭代器

下面來看看集合不可迭代的場(chǎng)景:

const favouriteMovies = {
  a: '哈利波特',
  b: '指環(huán)王',
  c: '尖峰時(shí)刻',
  d: '星際穿越',
  e: '速度與激情',
}

這個(gè)對(duì)象是不可迭代的。如果使用普通的 for 循環(huán)遍歷它,就會(huì)拋出錯(cuò)誤。隨著 ES6 中迭代器的引入,可以將其轉(zhuǎn)換為可迭代對(duì)象以便遍歷它。這些稱為自定義迭代器。下面看看如何實(shí)現(xiàn)對(duì)象的遍歷并打印出來:

favouriteMovies[Symbol.iterator] = function() {
  const ordered = Object.values(this).sort((a, b) => a - b);
  let i = 0;
  return {
    next: () => ({
      done: i >= ordered.length,
      value: ordered[i++]
    })
  }
}

for (const v of favouriteMovies) {
  console.log(v);
}

輸出結(jié)果如下:

哈利波特
指環(huán)王
尖峰時(shí)刻
星際穿越
速度與激情

這里使用 Symbol.iterator() 來定義迭代器。任何具有 Symbol.iterator 鍵的結(jié)構(gòu)都是可迭代的。

可迭代對(duì)象具有以下行為:

  1. 當(dāng) for..of 循環(huán)開始時(shí),它首先查找錯(cuò)誤。如果未找到,則它會(huì)訪問方法和定義該方法的對(duì)象。
  2. 以 for..of 循環(huán)方式迭代該對(duì)象。
  3. 使用該輸出對(duì)象的 next() 方法來獲取要返回的下一個(gè)值。
  4. 返回的值的格式為 done:boolean, value: any。返回 done:true 時(shí)循環(huán)結(jié)束。

下面來創(chuàng)建一個(gè) LeapYear 對(duì)象,該對(duì)象返回范圍為 (start, end) 的閏年列表,并在后續(xù)閏年之間設(shè)置間隔。

class LeapYear {
  constructor(start = 2020, end = 2040, interval = 4) {
    this.start = start;
    this.end = end;
    this.interval = interval;
  }
  [Symbol.iterator]() {
    let nextLeapYear = this.start;
    return {
      next: () => {
        if (nextLeapYear <= this.end) {
          let result = { value: nextLeapYear, done: false };
          nextLeapYear += this.interval;
          return result;
        }
        return { value: undefined, done: true };
      },
    };
  }
}

在上面的代碼中,為自定義類型 LeapYear 實(shí)現(xiàn)了 Symbol.iterator() 方法。分別在 this.start 和 this.end 字段中有迭代的起點(diǎn)和終點(diǎn)。使用 this.interval來跟蹤迭代的第一個(gè)元素和下一個(gè)元素之間的間隔。

現(xiàn)在,可以在自定義類型上調(diào)用 for...of 循環(huán),并查看其行為和輸出值,就像默認(rèn)數(shù)組類型一樣:

let leapYears = new LeapYear();
for (const leapYear of leapYears) {
    console.log(leapYear);
}

輸出結(jié)果如下:

2020
2024
2028
2032
2036
2040

這里的 LeapYear 通過 Symbol.iterator() 變成了可迭代對(duì)象。

在一些情況下,迭代器會(huì)比普通迭代更好。例如,在沒有隨機(jī)訪問的有序集合(如數(shù)組)中,迭代器的性能會(huì)更好,因?yàn)樗梢灾苯痈鶕?jù)當(dāng)前位置檢索元素。但是,對(duì)于無序集合,由于沒有順序,就不會(huì)體驗(yàn)到性能上的重大差異。

使用普通循環(huán)算法,例如 for 循環(huán)或 while 循環(huán),您只能循環(huán)遍歷允許迭代的集合:

const favourtieMovies = [
  '哈利波特',
  '指環(huán)王',
  '尖峰時(shí)刻', 
  '星際穿越',
  '速度與激情',
];

for (let i=0; i < favourtieMovies.length; i++) {
  console.log(favouriteMovies[i]);
}

let i = 0;
while (i < favourtieMovies.length) {
  console.log(favourtieMovies[i]);
  i++;
}

由于數(shù)組是可迭代的,因此可以使用 for 循環(huán)遍歷。我們也可以為上面實(shí)現(xiàn)一個(gè)迭代器,這將允許更好地訪問基于當(dāng)前位置的元素,而無需加載整個(gè)集合。代碼如下:

const iterator = favourtieMovies[Symbol.iterator]();
 
iterator.next();  // { value: '哈利波特', done: false }
iterator.next();  // { value: '指環(huán)王', done: false }
iterator.next();  // { value: '尖峰時(shí)刻', done: false }
iterator.next();  // { value: '星際穿越', done: false }
iterator.next();  // { value: '速度與激情', done: false }
iterator.next();  // { value: undefined, done: true }

next() 方法將返回迭代器的結(jié)果。它包括兩個(gè)值;集合中的元素和完成狀態(tài)。可以看到,當(dāng)遍歷完成后,即使訪問數(shù)組外的元素,也不會(huì)拋出錯(cuò)誤。它只會(huì)返回一個(gè)具有 undefined 值和完成狀態(tài)為 true 的對(duì)象。

(3)使用場(chǎng)景

那為什么向自定義對(duì)象中添加迭代器呢?我們也可以編寫自定義函數(shù)來遍歷對(duì)象以完成同樣的事情。

實(shí)際上,迭代器是一種標(biāo)準(zhǔn)化自定義對(duì)象的優(yōu)雅實(shí)現(xiàn)方式,它為自定義數(shù)據(jù)結(jié)構(gòu)提供了一種在更大的 JS 環(huán)境中很好地工作的方法。因此,提供自定義數(shù)據(jù)結(jié)構(gòu)的庫(kù)經(jīng)常會(huì)使用迭代器。例如,  Immutable.JS 庫(kù)就使用迭代器為其自定義對(duì)象(如Map)。所以,如果需要為封裝良好的自定義數(shù)據(jù)結(jié)構(gòu)提供原生迭代功能,就考慮使用迭代器。

2. 異步迭代器

JavaScript 中的異步迭代對(duì)象是實(shí)現(xiàn) Symbol.asyncIterator 的對(duì)象:

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
 
  }
};

我們可以將一個(gè)函數(shù)分配給 [Symbol.asyncIterator] 以返回一個(gè)迭代器對(duì)象。迭代器對(duì)象應(yīng)符合帶有 next() 方法的迭代器協(xié)議(類似于同步迭代器)。

下面來添加迭代器:

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
    let count = 0;

    return {
      next() {
        count++;
        if (count <= 3) {
          return Promise.resolve({ value: count, done: false });
        }

        return Promise.resolve({ value: count, done: true });
      }
    };
  }
};

這里用 Promise.resolve 包裝了返回的對(duì)象。下面來執(zhí)行 next() 方法:

const go = asyncIterable[Symbol.asyncIterator]();

go.next().then(iterator => console.log(iterator.value));
go.next().then(iterator => console.log(iterator.value));

輸出結(jié)果如下:

1
2

也可以使用 for await...of 來對(duì)異步迭代對(duì)象進(jìn)行迭代:

async function consumer() {
  for await (const asyncIterableElement of asyncIterable) {
    console.log(asyncIterableElement);
  }
}

consumer();

異步迭代器和迭代器是異步生成器的基礎(chǔ),后面會(huì)介紹異步生成器。

生成器

JavaScript 中的生成器可以分別兩種:同步生成器和異步生成器。

1. 同步生成器

(1)基本概念

生成器是一個(gè)可以暫停和恢復(fù)并可以產(chǎn)生多個(gè)值的過程。JavaScript 中的生成器由一個(gè)生成器函數(shù)組成,它返回一個(gè)可迭代 Generator 對(duì)象。

生成器是對(duì) JavaScript 的強(qiáng)大補(bǔ)充。它們可以維護(hù)狀態(tài),提供一種制作迭代器的有效方法,并且能夠處理無限數(shù)據(jù)流,可用于在前端實(shí)現(xiàn)無限滾動(dòng)等。此外,當(dāng)與 Promises 一起使用時(shí),生成器可以模擬 async/await 功能,這使我們能夠以更直接和可讀的方式處理異步代碼。盡管 async/await 是處理常見、簡(jiǎn)單的異步用例(例如從 API 獲取數(shù)據(jù))的一種更普遍的方式,但生成器具有更高級(jí)的功能。

生成器函數(shù)是返回生成器對(duì)象的函數(shù),由 function 關(guān)鍵字后面跟星號(hào) (*) 定義,如下所示:

function* generatorFunction() {}

有時(shí),我們可能會(huì)在函數(shù)名稱旁邊看到星號(hào),而不是 function 關(guān)鍵字,例如 function *generatorFunction(),它的工作原理是相同的,但 function* 是一種更廣泛接受的語法。

生成器函數(shù)也可以在表達(dá)式中定義,就像常規(guī)函數(shù)一樣:

const generatorFunction = function* () {}

生成器甚至可以是對(duì)象或類的方法:

// 生成器作為對(duì)象的方法
const generatorObj = {
  *generatorMethod() {},
}

// 生成器作為類的方法
class GeneratorClass {
  *generatorMethod() {}
}

下面的例子都將使用生成器函數(shù)聲明得語法。

注意:與常規(guī)函數(shù)不同,生成器不能使用 new 關(guān)鍵字構(gòu)造,也不能與箭頭函數(shù)結(jié)合使用。

現(xiàn)在我們知道了如何聲明生成器函數(shù),下面來看看生成器返回的可迭代生成器對(duì)象。

(2)生成器對(duì)象

傳統(tǒng)的 JavaScript 函數(shù)會(huì)在遇到return 關(guān)鍵字時(shí)返回一個(gè)值。如果省略 return 關(guān)鍵字,函數(shù)將隱式返回 undefined。

例如,在下面的代碼中,我們聲明了一個(gè) sum() 函數(shù),它返回一個(gè)值,該值是兩個(gè)整數(shù)參數(shù)的和:

function sum(a, b) {
  return a + b
}

調(diào)用該函數(shù)會(huì)返回一個(gè)值,該值是參數(shù)的總和:

const value = sum(5, 6) // 11

而生成器函數(shù)不會(huì)立即返回值,而是返回一個(gè)可迭代的生成器對(duì)象。在下面的例子中,我們聲明了一個(gè)函數(shù)并給它一個(gè)單一的返回值,就像一個(gè)標(biāo)準(zhǔn)的函數(shù):

function* generatorFunction() {
  return 'Hello, Generator!'
}

當(dāng)調(diào)用生成器函數(shù)時(shí),它將返回生成器對(duì)象,我們可以將其分配給一個(gè)變量:

const generator = generatorFunction()

如果這是一個(gè)常規(guī)函數(shù),我們希望生成器為我們提供函數(shù)中返回的字符串。然而,我們實(shí)際得到的是一個(gè)處于掛起狀態(tài)的對(duì)象。因此,調(diào)用生成器將提供類似于以下內(nèi)容的輸出:

generatorFunction {<suspended>}
  [[GeneratorLocation]]: VM335:1
  [[Prototype]]: Generator
  [[GeneratorState]]: "suspended"
  [[GeneratorFunction]]: ?* generatorFunction()
  [[GeneratorReceiver]]: Window

函數(shù)返回的生成器對(duì)象是一個(gè)迭代器。迭代器是一個(gè)具有可用的 next() 方法的對(duì)象,該方法用于迭代一系列值。next() 方法返回一個(gè)對(duì)象,其包含兩個(gè)屬性:

  • value:當(dāng)前步驟的值;
  • done:布爾值,指示生成器中是否有更多值。

next() 方法必須遵循以下規(guī)則:

  • 返回一個(gè)帶有 done: false 的對(duì)象來繼續(xù)迭代;
  • 返回一個(gè)帶有 done: true 的對(duì)象來停止迭代。

下面就來在生成器上調(diào)用 next() 并獲取迭代器的當(dāng)前值和狀態(tài):

generator.next()

這將得到以下輸出結(jié)果:

{value: "Hello, Generator!", done: true}

調(diào)用 next() 時(shí)的返回值為 Hello, Generator!,并且 done 的狀態(tài)為 true,因?yàn)樵撝祦碜躁P(guān)閉迭代器的返回值。由于迭代器完成,生成器函數(shù)的狀態(tài)將從掛起變?yōu)殛P(guān)閉。這時(shí)再次調(diào)用生成器將輸出以下內(nèi)容:

generatorFunction {<closed>}

除此之外,生成器函數(shù)也有區(qū)別于普通函數(shù)的獨(dú)特特征。下面我們就來了解一下 yield 運(yùn)算符并看看生成器如何暫停和恢復(fù)執(zhí)行。

(3)yield 運(yùn)算符

生成器為 JavaScript 引入了一個(gè)新的關(guān)鍵字:yield。**yield**** 可以暫停生成器函數(shù)并返回 **yield** 之后的值,從而提供一種輕量級(jí)的方法來遍歷值。**

在下面的例子中,我們將使用不同的值暫停生成器函數(shù)三次,并在最后返回一個(gè)值。然后將生成器對(duì)象分配給 generator 變量。

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'

  return 'Hello, Generator!'
}

const generator = generatorFunction()

現(xiàn)在,當(dāng)我們?cè)谏善骱瘮?shù)上調(diào)用 next() 時(shí),它會(huì)在每次遇到 yield 時(shí)暫停。done 會(huì)在每次 yield 后設(shè)置為 false,表示生成器還沒有結(jié)束。一旦遇到 return,或者函數(shù)中沒有更多的 yield 時(shí),done 就會(huì)變?yōu)?nbsp;true,生成器函數(shù)就結(jié)束了。

連續(xù)四次調(diào)用 next() 方法:

generator.next()
generator.next()
generator.next()
generator.next()

這些將按順序得到以下結(jié)果:

{value: "One", done: false}
{value: "Two", done: false}
{value: "Three", done: false}
{value: "Hello, Generator!", done: true}

next() 非常適合從迭代器對(duì)象中提取有限數(shù)據(jù)。

注意,生成器不需要 return;如果省略,最后一次迭代將返回 {value: undefined, done: true},生成器完成后對(duì) next() 的任何后續(xù)調(diào)用也是如此。

(4)遍歷生成器

使用 next() 方法可以遍歷生成器對(duì)象,接收完整對(duì)象的所有 value 和 done 屬性。不過,就像 Array、Map 和 Set 一樣,Generator 遵循迭代協(xié)議,并且可以使用 for...of 進(jìn)行迭代:

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'

  return 'Hello, Generator!'
}

const generator = generatorFunction()

for (const value of generator) {
  console.log(value)
}

輸出結(jié)果如下:

One
Two
Three

擴(kuò)展運(yùn)算符也可用于將生成器的值分配給數(shù)組:

const values = [...generator]

console.log(values)

輸出結(jié)果如下:

['One', 'Two', 'Three']

可以看到,擴(kuò)展運(yùn)算符和 for...of 都不會(huì)將 return 的值計(jì)入 value。

注意:雖然這兩種方法對(duì)于有限生成器都是有效的,但如果生成器正在處理無限數(shù)據(jù)流,則無法在不創(chuàng)建無限循環(huán)的情況下直接使用擴(kuò)展運(yùn)算符或 for...of。

我們還可以從迭代結(jié)果中解構(gòu)值:

const [a, b, c]= generator;
console.log(a);
console.log(b);
console.log(c);

輸出結(jié)果如下:

One
Two
Three

(5)關(guān)閉生成器

如我們所見,生成器可以通過遍歷其所有值將其 done 屬性設(shè)置為 true 并將其狀態(tài)設(shè)置為 closed 。除此之外,還有兩種方法可以立即關(guān)閉生成器:使用 return() 方法和使用 throw() 方法。

使用 return(),生成器可以在任何時(shí)候終止,就像在函數(shù)體中的 return 語句一樣??梢詫?shù)傳遞給 return(),或?qū)⑵淞艨找员硎疚炊x的值。

下面來創(chuàng)建一個(gè)具有 yield 值但在函數(shù)定義中沒有 return 的生成器:

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'
}

const generator = generatorFunction()

第一個(gè) next() 將返回“One”,并將 done 設(shè)置為 false。如果在那之后立即在生成器對(duì)象上調(diào)用 return() 方法,將獲得傳遞的值并將 done 設(shè)置為 true。對(duì) next() 的任何額外調(diào)用都會(huì)給出默認(rèn)的已完成生成器響應(yīng),其中包含一個(gè) undefined 值。

generator.next()
generator.return('Return!')
generator.next()

輸出結(jié)果如下:

{value: "Neo", done: false}
{value: "Return!", done: true}
{value: undefined, done: true}

return() 方法會(huì)強(qiáng)制生成器對(duì)象完成并忽略任何其他 yield 關(guān)鍵字。當(dāng)需要使函數(shù)可取消時(shí),這在異步編程中特別有用,例如當(dāng)用戶想要執(zhí)行不同的操作時(shí)中斷數(shù)據(jù)請(qǐng)求,因?yàn)闊o法直接取消 Promise。

如果生成器函數(shù)的主體有捕獲和處理錯(cuò)誤的方法,則可以使用 throw() 方法將錯(cuò)誤拋出到生成器中。這將啟動(dòng)生成器,拋出錯(cuò)誤并終止生成器。

下面來在生成器函數(shù)體內(nèi)放一個(gè) try...catch 并在發(fā)現(xiàn)錯(cuò)誤時(shí)記錄錯(cuò)誤:

function* generatorFunction() {
  try {
    yield 'One'
   yield 'Two'
  } catch (error) {
    console.log(error)
  }
}

const generator = generatorFunction()

現(xiàn)在來運(yùn)行 next() 方法,然后運(yùn)行 throw() 方法:

generator.next()
generator.throw(new Error('Error!'))

輸出結(jié)果如下:

{value: "One", done: false}
Error: Error!
{value: undefined, done: true}

使用 throw() 可以將錯(cuò)誤注入到生成器中,該錯(cuò)誤被 try...catch 捕獲并記錄到控制臺(tái)。

(6)生成器對(duì)象方法和狀態(tài)

下面是生成器對(duì)象的方法:

  • next():返回生成器中的后面的值;
  • return():在生成器中返回一個(gè)值并結(jié)束生成器;
  • throw():拋出錯(cuò)誤并結(jié)束生成器。

下面是生成器對(duì)象的狀態(tài):

  • suspended:生成器已停止執(zhí)行但尚未終止。
  • closed:生成器因遇到錯(cuò)誤、返回或遍歷所有值而終止。

(7)yield 委托

除了常規(guī)的 yield 運(yùn)算符之外,生成器還可以使用 yield* 表達(dá)式將更多值委托給另一個(gè)生成器。當(dāng)在生成器中遇到 yield* 時(shí),它將進(jìn)入委托生成器并開始遍歷所有 yield 直到該生成器關(guān)閉。這可以用于分離不同的生成器函數(shù)以在語義上組織代碼,同時(shí)仍然讓它們的所有 **yield** 都可以按正確的順序迭代。

下面來創(chuàng)建兩個(gè)生成器函數(shù),其中一個(gè)將對(duì)另一個(gè)進(jìn)行 yield* 操作:

function* delegate() {
  yield 3
  yield 4
}

function* begin() {
  yield 1
  yield 2
  yield* delegate()
}

接下來,遍歷 begin() 生成器函數(shù):

const generator = begin()

for (const value of generator) {
  console.log(value)
}

輸出結(jié)果如下:

1
2
3
4

外部的生成器(begin)生成值 1 和 2,然后使用 yield* 委托給另一個(gè)生成器(delegate),返回 3 和 4。

yield* 還可以委托給任何可迭代的對(duì)象,例如 Array 或 Map。yield 委托有助于組織代碼,因?yàn)樯善髦腥魏蜗胍褂?nbsp;yield 的函數(shù)也必須是一個(gè)生成器。

(8)在生成器中傳遞值

上面的例子中,我們使用生成器作為迭代器,并且在每次迭代中產(chǎn)生值。除了產(chǎn)生值之外,生成器還可以使用 next() 中的值。在這種情況下,yield 將包含一個(gè)值。

需要注意,調(diào)用的第一個(gè) next() 不會(huì)傳遞值,而只會(huì)啟動(dòng)生成器。為了證明這一點(diǎn),可以記錄 yield 的值并使用一些值調(diào)用 next() 幾次。

function* generatorFunction() {
  console.log(yield)
  console.log(yield)

  return 'End'
}

const generator = generatorFunction()

generator.next()
generator.next(100)
generator.next(200)

輸出結(jié)果如下:

100
200
{value: "End", done: true}

除此之外,也可以為生成器提供初始值。下面來創(chuàng)建一個(gè) for 循環(huán)并將每個(gè)值傳遞給 next() 方法,同時(shí)將一個(gè)參數(shù)傳遞給 inital 函數(shù):

function* generatorFunction(value) {
  while (true) {
    value = yield value * 10
  }
}

const generator = generatorFunction(0)

for (let i = 0; i < 5; i++) {
  console.log(generator.next(i).value)
}

這將從 next() 中檢索值并為下一次迭代生成一個(gè)新值,該值是前一個(gè)值乘以 10。輸出結(jié)果如下:

0
10
20
30
40

處理啟動(dòng)生成器的另一種方法是將生成器包裝在一個(gè)函數(shù)中,該函數(shù)將會(huì)在執(zhí)行任何其他操作之前調(diào)用 next() 一次。

(9)async/await

async/await 使處理異步數(shù)據(jù)更簡(jiǎn)單、更容易理解。生成器具有比異步函數(shù)更廣泛的功能,但能夠復(fù)制類似的行為。以這種方式實(shí)現(xiàn)異步編程可以增加代碼的靈活性。

下面來構(gòu)建一個(gè)異步函數(shù),它使用 Fetch API 獲取數(shù)據(jù)并將響應(yīng)記錄到控制臺(tái)。

首先定義一個(gè)名為 getUsers 的異步函數(shù),該函數(shù)從 API 獲取數(shù)據(jù)并返回一個(gè)對(duì)象數(shù)組,然后調(diào)用 getUsers:

const getUsers = async function () {
  const response = await fetch('https://jsonplaceholder.typicode.com/users')
  const json = await response.json()

  return json
}

getUsers().then((response) => console.log(response))

輸出結(jié)果如下:

圖片圖片

使用生成器可以創(chuàng)建幾乎相同但不使用 async/await 關(guān)鍵字的效果。相反,它將使用我們創(chuàng)建的新函數(shù),并產(chǎn)生值而不是等待 Promise。

const getUsers = asyncAlt(function* () {
  const response = yield fetch('https://jsonplaceholder.typicode.com/users')
  const json = yield response.json()

  return json
})

getUsers().then((response) => console.log(response))

如我們所見,它看起來與 async/await 實(shí)現(xiàn)幾乎相同,除了有一個(gè)生成器函數(shù)被傳入以產(chǎn)生值。

現(xiàn)在可以創(chuàng)建一個(gè)類似于異步函數(shù)的 asyncAlt 函數(shù)。asyncAlt 有一個(gè) generatorFunction 參數(shù),它是產(chǎn)生 fetch 返回的 Promise 的函數(shù)。asyncAlt 返回函數(shù)本身,并 resolve 它得到的每個(gè) Promise,直到最后一個(gè):

function asyncAlt(generatorFunction) {
  return function () {
    // 創(chuàng)建并分配生成器對(duì)象
    const generator = generatorFunction()

    // 定義一個(gè)接受生成器下一次迭代的函數(shù)
    function resolve(next) {
      // 如果生成器關(guān)閉并且沒有更多的值可以生成,則解析最后一個(gè)值
      if (next.done) {
        return Promise.resolve(next.value)
      }

      // 如果仍有值可以產(chǎn)生,那么它們就是Promise,必須 resolved。
      return Promise.resolve(next.value).then((response) => {
        return resolve(generator.next(response))
      })
    }

    // 開始 resolve Promise
    return resolve(generator.next())
  }
}

這樣就會(huì)得到和async/await一樣的結(jié)果:

圖片圖片

盡管這個(gè)方法可以為代碼增加靈活性,但通常 async/await 是更好的選擇,因?yàn)樗橄罅藢?shí)現(xiàn)細(xì)節(jié)并讓開發(fā)者專注于編寫高效代碼。

(10)使用場(chǎng)景

很多開發(fā)人員認(rèn)為生成器函數(shù)視為一種奇特的 JavaScript 功能,在現(xiàn)實(shí)中幾乎沒有應(yīng)用。在大多數(shù)情況下,確實(shí)用不到生成器。

生成器的優(yōu)點(diǎn):

  • 惰性求值:除非需要,否則不計(jì)算值。它提供按需計(jì)算。只有需要它時(shí),value 才會(huì)存在。
  • 內(nèi)存效率高:由于惰性求值,生成器的內(nèi)存效率非常高,因?yàn)樗粫?huì)為預(yù)先生成的未使用值分配不必要的內(nèi)存位置。
  • 更簡(jiǎn)潔的代碼:生成器提供更簡(jiǎn)潔的代碼,尤其是在異步行為中。

生成器在對(duì)性能要求高的場(chǎng)景中有很大的用處。特別是,它們適用于以下場(chǎng)景:

  • 處理大文件和數(shù)據(jù)集。
  • 生成無限的數(shù)據(jù)序列。
  • 按需計(jì)算昂貴的邏輯。

Redux sagas 就是實(shí)踐中使用的生成器的一個(gè)很好的例子。它是一個(gè)用于管理redux應(yīng)用異步操作的中間件,redux-saga 通過創(chuàng)建 sagas 將所有異步操作邏輯收集在一個(gè)地方集中處理,可以用來代替 redux-thunk 中間件。

2. 異步生成器

ECMAScript 2018 中引入了異步生成器的概念,它是一種特殊類型的異步函數(shù),可以隨意停止和恢復(fù)其執(zhí)行。

同步生成器函數(shù)和異步生成器函數(shù)的區(qū)別在于,后者從迭代器對(duì)象返回一個(gè)異步的、基于 Promise 的結(jié)果。

要想創(chuàng)建異步生成器函數(shù),需要聲明一個(gè)帶有星號(hào) * 的生成器函數(shù),前綴為 async:

async function* asyncGenerator() {

}

一旦進(jìn)入函數(shù),就可以使用 yield 來暫停執(zhí)行:

async function* asyncGenerator() {
  yield 'One'
  yield 'Two'
}

這里 yield 會(huì)暫停執(zhí)行并返回一個(gè)迭代器對(duì)象給調(diào)用者。這個(gè)對(duì)象既是可迭代對(duì)象,又是迭代器。

異步生成器函數(shù)不會(huì)像常規(guī)函數(shù)那樣在一步中計(jì)算出所有結(jié)果。相反,它會(huì)逐步提取值。我們可以使用兩種方法從異步生成器解析 Promise:

  • 在迭代器對(duì)象上調(diào)用 next();
  • 使用 for await...of 異步迭代。

對(duì)于上面的例子,可以這樣做:

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}

const go = asyncGenerator();

go.next().then(iterator => console.log(iterator.value));
go.next().then(iterator => console.log(iterator.value));

輸出結(jié)果如下:

'One';
'Two'

另一種方法使用異步迭代 for await...of。要使用異步迭代,需要用 async 函數(shù)包裝它:

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();

for await...of 非常適合提取非有限數(shù)據(jù)流。

責(zé)任編輯:武曉燕 來源: 前端充電寶
相關(guān)推薦

2023-03-01 00:07:32

JavaScript迭代器生成器

2017-06-26 16:26:15

Python迭代對(duì)象迭代器

2023-11-15 13:35:00

迭代器生成器Python

2010-07-20 13:56:26

Python迭代器生成器

2024-11-11 06:10:00

Python生成器迭代器

2024-05-10 11:31:59

Python迭代器生成器

2010-08-19 10:12:34

路由器標(biāo)準(zhǔn)

2023-05-05 08:53:38

迭代器生成器Python

2011-03-29 15:44:41

對(duì)日軟件外包

2021-07-12 07:01:39

AST前端abstract sy

2020-11-30 06:27:35

Java泛型Object

2020-09-16 07:59:40

數(shù)組內(nèi)存

2020-04-20 10:55:57

大數(shù)據(jù)人工智能技術(shù)

2024-11-01 15:51:06

2020-07-07 07:34:29

RedisSDS數(shù)據(jù)結(jié)構(gòu)

2019-11-21 15:08:13

DevOps云計(jì)算管理

2019-04-03 09:10:35

Rediskey-value數(shù)據(jù)庫(kù)

2014-06-16 10:03:54

分組交換

2020-10-10 09:19:58

JavaScript開發(fā)技術(shù)

2017-09-06 09:26:03

Python生成器協(xié)程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)