async function_理解 Iterator, Generator 和 Async/Await
戳藍字「前端技術優選」關注我們哦!
這里重點理解他們三者分別是什么,有什么區別,以及分別適用什么場景
Iterator
Iterator是最簡單最好理解的,在很久之前我寫過一篇文章 循環的秘密 里面討論了Iterator原理,有興趣可以看一下。簡單的說,我們常用的 for in ?循環,都是通過調用被循環對象的一個特殊函數 Iterator 來實現的,但是以前這個函數是隱藏的我們無法訪問, 從 Symbol 引入之后,我們就可以通過 Symbol.iterator 來直接讀寫這個特殊函數。
對于循環語句來說,他并不關心被循環的對象到底是什么,他只負責調用 data[Symbol.iterator] 函數,然后根據返回值來進行循環。所以任何對象只要提供了標準的 Iterator 接口即可被循環,比如我們現在來創造一個自定義的數據:
var?students?=?{}students[Symbol.iterator]?=?function()?{
??let?index?=?1;
??return?{?next()?{
????return?{done:?index<100,?value:?index++}?}
??}
}
for(var?i?of?students)?{?console.log(i);?}
除了這種方式外,我們也可以通過 Generator 來實現一個 Iterator 接口。
Generator 基本語法
Generator 是ES6引入的新語法,Generator是一個可以暫停和繼續執行的函數。簡單的用法,可以當做一個Iterator來用,進行一些遍歷操作。復雜一些的用法,他可以在內部保存一些狀態,成為一個狀態機。
Generator 基本語法包含兩部分:
函數名前要加一個星號
函數內部用 yield 關鍵字返回值
下面是一個簡單的示例:
function * count() {yield 1yield 2return 3}var c = count()console.log(c.next()) // { value: 1, done: false }console.log(c.next()) // { value: 2, done: false }console.log(c.next()) // { value: 3, done: true }console.log(c.next()) // { value: undefined, done: true }
由于Generator也存在 Symbol.iterator 接口,所以他也可以被 for 循環調用:
function * count() {yield 1yield 2return 3}var c = count()for (i of c) console.log(i) // 1, 2
不過這里要注意一個不同點,調用 next 的時候能得到 3 ,但是用 for 則會忽略最后的 return 語句。 也就是 for 循環會忽略 generator 中的 return 語句.
另外 yeild* 語法可以用來在 Generator 中調用另一個 Generator,參見 yield* MDN
Generator VS Iterator
Generator 可以看做是一個更加靈活的 Iterator ,他們之間是可以互相替代的,但是, Generator 由于可以通過 yield 隨時暫停,因此可以很方便進行流程控制和狀態管理,而 Iterator 就可能需要你寫更多的代碼進行相同的操作:
比如 Stack Overflow 上的這個中序遍歷代碼:
function* traverseTree(node) {if (node == null) return;yield* traverseTree(node.left);yield node.value;yield* traverseTree(node.right);}
同樣的功能用 iterator 實現就會變得麻煩很多。
Generator 也是實現簡單的狀態機的最佳選擇,因為他是在函數內部進行 yield 操作,因此不會丟失當前狀態:
function * clock () {yield 'tick'yield 'tock'}
同樣的功能如果普通的函數,因為每次都是調用這個函數,所以函數內部并不能保存狀態,因此就需要在函數外面用一個變量來保存當前狀態:
let tick = falsefunction clock() {tick = !tickreturn tick ? 'tick' : 'tock'
}
其實Babel編譯 Generator 的時候,也是用了一個 Context 來保存當前狀態的,可以看看Babel編譯后的代碼,其中的 _context 就是當前狀態,這里通過 _context.next 的值來控制調用 next 的時候應該進入到哪一個流程:
var _marked = /*#__PURE__*/regeneratorRuntime.mark(clock);function clock() {return regeneratorRuntime.wrap(function clock$(_context) {while (1) {switch (_context.prev = _context.next) {case 0:_context.next = 2;return 'tick';case 2:_context.next = 4;return 'tock';case 4:case 'end':return _context.stop();}
}
}, _marked, this);
}
當然,如果是很復雜的,非線性狀態變化的狀態機,我還是會傾向于用一個類來實現。
Generator 異步操作
Generator 的設計,可以很方便執行異步操作,現在我們需要寫一個小函數,可以取到用戶信息然后打印出來,我們用generator來寫就是這樣的:
function * fetchUser () {const user = yield ajax()console.log(user)}
但是,generator本身并不會自動進行 next 操作,也就是,我們如果此時這樣調用并不能打印出用戶信息:
const f = fetchUser()因為Generator 本身只是一個狀態機,他需要由調用者來改變他的狀態,所以我們需要額外加一段控制代碼來控制 fetchUser 進行狀態轉換:
function * fetchUser () {const user = yield ajax()console.log(user)}const f = fetchUser()// 加入的控制代碼const result = f.next()result.value.then((d) => {f.next(d)
})
但是寫了這些代碼之后, Generator 的實現就變得非常不優雅了,如果我們內部有多個異步操作,控制代碼就會變得很長。我們可以選擇 co 庫來幫我們做這個操作。
Async/Await
我最開始接觸到 Async/Await 的時候把它當成了一個 promise 的語法糖,但是經過我們對 Generator 的理解后,明白了其實他就是 Generator 的一個語法糖:
async 對應的是 *
await 對應的是 yield
他只是自動幫我們進行了 Generator 的流程控制而已。
和上面的獲取用戶信息實現一樣的功能的話,基本語法如下:
async function fetchUser() {const user = await ajax()console.log(user)}
因為有自動的流程控制,所以我們不用手動在ajax成功的時候手動調用 next。相比于 Promise 或者 Generator 的實現,代碼要明顯更加優雅。
如果有興趣的話,可以參考一下 Babel 是如何編譯 Async/Await 的,簡單的說,代碼分成了兩部分,一部分是編譯了一個 Generator,另一部分是通過 promise 實現了generator的流程控制。
對于如下代碼:
async function count () {let a = await 1;let b = await 2;return a+b}
編譯后的代碼:
var count = function () {// 下面這部分是 generator 的一個實現var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {var a, b;return regeneratorRuntime.wrap(function _callee$(_context) {while (1) {switch (_context.prev = _context.next) {case 0:_context.next = 2;return 1;// 省略...}
}
}, _callee, this);
}));return function count() {return _ref.apply(this, arguments);
};
}();// 下面這部分是用 promise 實現了流程控制。function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
所以我們可以大約這么認為: async/await == generator + promise
async/await 并發
我們的代碼在執行到await的時候會等待結果返回才執行下一行,這樣如果我們有很多需要異步執行的操作就會變成一個串行的流程,可能會導致非常慢。
比如如下代碼,我們需要遍歷獲取redis中存儲的100個用戶的信息:
const users=[]for (var i=0;i<ids.length;i++) {users.push(await db.get(ids))}
由于每次數據庫讀取操作都要消耗時間,這個接口將會變得非常慢。如果我們把它變成一個并行的操作,將會極大提升效率:
const users = await Promise.all(ids.map(async (id) => await db.get(id)))總結
Iterator 是一個循環接口,任何實現了此接口的數據都可以被 for in 循環遍歷
Generator 是一個可以暫停和繼續執行的函數,他可以完全實現 Iterator 的功能,并且由于可以保存上下文,他非常適合實現簡單的狀態機。另外通過一些流程控制代碼的配合,可以比較容易進行異步操作。
Async/Await 就是generator進行異步操作的語法糖。而這個語法糖反而是被使用最廣泛的,比如著名的 Koa
參考
http://es6.ruanyifeng.com/#docs/generator-async
https://stackoverflow.com/questions/37124006/iterator-and-a-generator-in-javascript
https://stackoverflow.com/questions/23613612/what-can-we-do-with-es6-generator-that-we-cannot-with-for-loop/23614292#23614292
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
總結
以上是生活随笔為你收集整理的async function_理解 Iterator, Generator 和 Async/Await的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle sql练习_SQL入门学习
- 下一篇: html点击按钮弹出窗口_电脑桌面总是弹