日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

async function_理解 Iterator, Generator 和 Async/Await

發布時間:2024/9/19 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。