前端Tips#6 – 在 async iterator 上使用 for-await-of 語法糖
- 2020 年 3 月 12 日
- 筆記
影片講解
前往原文 前端Tips 專欄#6,點擊觀看
文字講解
本期主要是講解如何使用 for-await-of
語法糖進行非同步操作迭代,讓組織非同步操作的程式碼更加簡潔易讀。
1、場景簡述
以下程式碼中的 for...of
操作,列印順序 "2、3、4"(總共耗費時間 4s):
const delay = (time) => () => setTimeout(() => { console.log(time) }, time * 1000); const delays = [delay(3), delay(2), delay(4)]; for (cur of delays) { cur(); }
但我們想要以數組順序列印 「3、2、4」(總共耗時9s),請問該如何實現?
2、同步迭代器
以常見的數組列印為例,下述程式碼會依次列印出 "0、1":
for(const cur of [0, 1]){ console.log(cur); }
那麼如何用 同步迭代器 實現上述同等輸出?
Iterator
是 ECMAScript 2015 引進的功能,它就是一個 function
,只不過對這個 function
的形式有特殊的規定:
- 返回對象必須包含
next
屬性,該屬性也是function
- 該
next
函數返回值必須返回包含done
和value
這兩個欄位的對象
有了 Iterator
,就可以藉助 [Symbol.iterator]
構造出 可迭代對象(Iteratable):
// 返回一個可迭代對象,注意 [Symbol.iterator] 這個 key const someIteratable = { [Symbol.iterator]: someIterator }
凡是可迭代對象就可以使用 for...of
語法,所以這是一種層層迭進的關係。
3、使用迭代器實現數組列印
知道了迭代器的概念後,就可以藉助迭代器實現上述的數組列印功能,首先自定義構造出 countIterator
迭代器
let count = 0; function countIterator() { // 返回一個迭代器對象,對象的屬性是一個 next 方法 return { next: function () { if (count < 2) { // 當沒有到達末尾時,返回當前值,並把索引加1 return { value: count++, done: false }; } // 到達末尾,done 屬性為 true return { value: count, done: true }; } }; }
然後創建出可迭代對象,由於該對象的行為和 [0,1]
這個數組類似,所以起名為 customArray
:
// 返回一個可迭代對象,注意 [Symbol.iterator] 這個 key const customArray = { [Symbol.iterator]: countIterator }
最後給這個可迭代對象應用 for...of
即可,就能列印出 0、1
內容:
for (const cur of customArray) { console.log(cur) }
通過這個例子你就應該比較容易迭代器的理解,其實 JS 原生的String
、Array
、Map
和 Set
等都是可迭代對象,因為它們的原型對象都有一個 Symbol.iterator
方法。
4、非同步迭代器
理解了同步迭代器,那麼 非同步迭代器(Async Iterator)也就很容易理解了,它和同步迭代器的差別在於:
- 非同步迭代器必須返回 Promise 對象,且該 Promise 返回
{ value, done }
格式對象 - 非同步可迭代對象(Async Iteratable)用 Symbol.asyncIterator 作為 key
- 非同步可迭代對象(Async Iteratable)可用
for-await-of
進行迭代
Async iterator 是 ECMAScript 2018 引進的
藉助非同步迭代器就可以實現本期開頭所講的功能,實現自定義的 delayIteraterable
可迭代對象,它使用 [Symbol.asyncIterator]
作為 key,其 value 就是非同步迭代器:
const promisify = func => (...args) => new Promise((resolve, reject) => func(...args, (err, result) => (err ? reject(err) : resolve(result))) ); const delayIteraterable = { [Symbol.asyncIterator]: () => { return { next: () => { const cur = promisify(delays.shift()); return cur().then(res => { return { done: delays.length === 0, value: res } }); } } } }
這裡用到的 promisify 函數,具體可參考前端 Tips – 第 5 期的內容講解。
然後直接搭配 for-await-of
語法糖使用,就能進行非同步迭代,按我們的要求依次輸出 「3、2、4」(總共耗時9s)
const execIt = async function () { for await (const cur of delayIteraterable) { console.log(cur); } } execIt();
5、擴展:Generator & Async Generator
除了用迭代器生成 可迭代對象 外,還能用 Generator
(生成器)生成 可迭代對象,而且一般來講程式碼實現也更為緊湊。
由於時間關係就不展開了,感興趣的可閱讀文末的參考文章自行學習。本期的例子也提供了 generator 的版本可供參考,鏈接:https://github.com/boycgit/fe-program-tips/blob/master/src/6-async-iterator/async-yield.js
6、參考文檔
- for await…of:官方
for await...of
教程 - Asynchronous Iterators in JavaScript:通俗易懂的教程,條理清晰
- ES2018 新特徵之:非同步迭代器 for-await-of:ES 2018 系列教程中的非同步迭代器教程
- map for async iterators in JavaScript:Youtube 上的教程,使用非同步迭代器完成非同步 mapper 操作
附:如何獲取往期 「前端Tips」 列表
有兩種方式獲取歷史 tips:
① 在公眾號內回「tips」 + 「年份」 + 「A(或者B)」 獲取半年度 tips。例如:回復 「tips2020A」 即可獲取 2020 年上半年 tips 列表
② 前往網站:https://boycgit.github.io/fe-program-tips ,裡面提供了搜索功能
歡迎大家關注我的知識專欄,更多內容等你來挖掘
「可在微信內搜索 「JSCON簡時空」或 「iJSCON」 關注」