async function_理解 Iterator, Generator 和 Async/Await
戳藍(lán)字「前端技術(shù)優(yōu)選」關(guān)注我們哦!
這里重點(diǎn)理解他們?nèi)叻謩e是什么,有什么區(qū)別,以及分別適用什么場景
Iterator
Iterator是最簡單最好理解的,在很久之前我寫過一篇文章 循環(huán)的秘密 里面討論了Iterator原理,有興趣可以看一下。簡單的說,我們常用的 for in ?循環(huán),都是通過調(diào)用被循環(huán)對象的一個(gè)特殊函數(shù) Iterator 來實(shí)現(xiàn)的,但是以前這個(gè)函數(shù)是隱藏的我們無法訪問, 從 Symbol 引入之后,我們就可以通過 Symbol.iterator 來直接讀寫這個(gè)特殊函數(shù)。
對于循環(huán)語句來說,他并不關(guān)心被循環(huán)的對象到底是什么,他只負(fù)責(zé)調(diào)用 data[Symbol.iterator] 函數(shù),然后根據(jù)返回值來進(jìn)行循環(huán)。所以任何對象只要提供了標(biāo)準(zhǔn)的 Iterator 接口即可被循環(huán),比如我們現(xiàn)在來創(chuàng)造一個(gè)自定義的數(shù)據(jù):
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 來實(shí)現(xiàn)一個(gè) Iterator 接口。
Generator 基本語法
Generator 是ES6引入的新語法,Generator是一個(gè)可以暫停和繼續(xù)執(zhí)行的函數(shù)。簡單的用法,可以當(dāng)做一個(gè)Iterator來用,進(jìn)行一些遍歷操作。復(fù)雜一些的用法,他可以在內(nèi)部保存一些狀態(tài),成為一個(gè)狀態(tài)機(jī)。
Generator 基本語法包含兩部分:
函數(shù)名前要加一個(gè)星號(hào)
函數(shù)內(nèi)部用 yield 關(guān)鍵字返回值
下面是一個(gè)簡單的示例:
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 循環(huán)調(diào)用:
function * count() {yield 1yield 2return 3}var c = count()for (i of c) console.log(i) // 1, 2
不過這里要注意一個(gè)不同點(diǎn),調(diào)用 next 的時(shí)候能得到 3 ,但是用 for 則會(huì)忽略最后的 return 語句。 也就是 for 循環(huán)會(huì)忽略 generator 中的 return 語句.
另外 yeild* 語法可以用來在 Generator 中調(diào)用另一個(gè) Generator,參見 yield* MDN
Generator VS Iterator
Generator 可以看做是一個(gè)更加靈活的 Iterator ,他們之間是可以互相替代的,但是, Generator 由于可以通過 yield 隨時(shí)暫停,因此可以很方便進(jìn)行流程控制和狀態(tài)管理,而 Iterator 就可能需要你寫更多的代碼進(jìn)行相同的操作:
比如 Stack Overflow 上的這個(gè)中序遍歷代碼:
function* traverseTree(node) {if (node == null) return;yield* traverseTree(node.left);yield node.value;yield* traverseTree(node.right);}
同樣的功能用 iterator 實(shí)現(xiàn)就會(huì)變得麻煩很多。
Generator 也是實(shí)現(xiàn)簡單的狀態(tài)機(jī)的最佳選擇,因?yàn)樗窃诤瘮?shù)內(nèi)部進(jìn)行 yield 操作,因此不會(huì)丟失當(dāng)前狀態(tài):
function * clock () {yield 'tick'yield 'tock'}
同樣的功能如果普通的函數(shù),因?yàn)槊看味际钦{(diào)用這個(gè)函數(shù),所以函數(shù)內(nèi)部并不能保存狀態(tài),因此就需要在函數(shù)外面用一個(gè)變量來保存當(dāng)前狀態(tài):
let tick = falsefunction clock() {tick = !tickreturn tick ? 'tick' : 'tock'
}
其實(shí)Babel編譯 Generator 的時(shí)候,也是用了一個(gè) Context 來保存當(dāng)前狀態(tài)的,可以看看Babel編譯后的代碼,其中的 _context 就是當(dāng)前狀態(tài),這里通過 _context.next 的值來控制調(diào)用 next 的時(shí)候應(yīng)該進(jìn)入到哪一個(gè)流程:
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);
}
當(dāng)然,如果是很復(fù)雜的,非線性狀態(tài)變化的狀態(tài)機(jī),我還是會(huì)傾向于用一個(gè)類來實(shí)現(xiàn)。
Generator 異步操作
Generator 的設(shè)計(jì),可以很方便執(zhí)行異步操作,現(xiàn)在我們需要寫一個(gè)小函數(shù),可以取到用戶信息然后打印出來,我們用generator來寫就是這樣的:
function * fetchUser () {const user = yield ajax()console.log(user)}
但是,generator本身并不會(huì)自動(dòng)進(jìn)行 next 操作,也就是,我們?nèi)绻藭r(shí)這樣調(diào)用并不能打印出用戶信息:
const f = fetchUser()因?yàn)镚enerator 本身只是一個(gè)狀態(tài)機(jī),他需要由調(diào)用者來改變他的狀態(tài),所以我們需要額外加一段控制代碼來控制 fetchUser 進(jìn)行狀態(tài)轉(zhuǎn)換:
function * fetchUser () {const user = yield ajax()console.log(user)}const f = fetchUser()// 加入的控制代碼const result = f.next()result.value.then((d) => {f.next(d)
})
但是寫了這些代碼之后, Generator 的實(shí)現(xiàn)就變得非常不優(yōu)雅了,如果我們內(nèi)部有多個(gè)異步操作,控制代碼就會(huì)變得很長。我們可以選擇 co 庫來幫我們做這個(gè)操作。
Async/Await
我最開始接觸到 Async/Await 的時(shí)候把它當(dāng)成了一個(gè) promise 的語法糖,但是經(jīng)過我們對 Generator 的理解后,明白了其實(shí)他就是 Generator 的一個(gè)語法糖:
async 對應(yīng)的是 *
await 對應(yīng)的是 yield
他只是自動(dòng)幫我們進(jìn)行了 Generator 的流程控制而已。
和上面的獲取用戶信息實(shí)現(xiàn)一樣的功能的話,基本語法如下:
async function fetchUser() {const user = await ajax()console.log(user)}
因?yàn)橛凶詣?dòng)的流程控制,所以我們不用手動(dòng)在ajax成功的時(shí)候手動(dòng)調(diào)用 next。相比于 Promise 或者 Generator 的實(shí)現(xiàn),代碼要明顯更加優(yōu)雅。
如果有興趣的話,可以參考一下 Babel 是如何編譯 Async/Await 的,簡單的說,代碼分成了兩部分,一部分是編譯了一個(gè) Generator,另一部分是通過 promise 實(shí)現(xiàn)了generator的流程控制。
對于如下代碼:
async function count () {let a = await 1;let b = await 2;return a+b}
編譯后的代碼:
var count = function () {// 下面這部分是 generator 的一個(gè)實(shí)現(xiàn)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 實(shí)現(xiàn)了流程控制。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"); }); }; }
所以我們可以大約這么認(rèn)為: async/await == generator + promise
async/await 并發(fā)
我們的代碼在執(zhí)行到await的時(shí)候會(huì)等待結(jié)果返回才執(zhí)行下一行,這樣如果我們有很多需要異步執(zhí)行的操作就會(huì)變成一個(gè)串行的流程,可能會(huì)導(dǎo)致非常慢。
比如如下代碼,我們需要遍歷獲取redis中存儲(chǔ)的100個(gè)用戶的信息:
const users=[]for (var i=0;i<ids.length;i++) {users.push(await db.get(ids))}
由于每次數(shù)據(jù)庫讀取操作都要消耗時(shí)間,這個(gè)接口將會(huì)變得非常慢。如果我們把它變成一個(gè)并行的操作,將會(huì)極大提升效率:
const users = await Promise.all(ids.map(async (id) => await db.get(id)))總結(jié)
Iterator 是一個(gè)循環(huán)接口,任何實(shí)現(xiàn)了此接口的數(shù)據(jù)都可以被 for in 循環(huán)遍歷
Generator 是一個(gè)可以暫停和繼續(xù)執(zhí)行的函數(shù),他可以完全實(shí)現(xiàn) Iterator 的功能,并且由于可以保存上下文,他非常適合實(shí)現(xiàn)簡單的狀態(tài)機(jī)。另外通過一些流程控制代碼的配合,可以比較容易進(jìn)行異步操作。
Async/Await 就是generator進(jìn)行異步操作的語法糖。而這個(gè)語法糖反而是被使用最廣泛的,比如著名的 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
總結(jié)
以上是生活随笔為你收集整理的async function_理解 Iterator, Generator 和 Async/Await的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle sql练习_SQL入门学习
- 下一篇: html点击按钮弹出窗口_电脑桌面总是弹