Generator简单了解
Generator是一個生成器,它生成的到底是什么呢?
Ta生成的就是一個 Iterator對象 。
function *gen() {yield 1;yield 2;return 3; }const it = gen();console.log(it.next()); // { value: 1, done: false } console.log(it.next()); // { value: 2, done: false } console.log(it.next()); // { value: 3, done: false } console.log(it.next()); // { value: undefined, done: true } console.log(it.next()); // { value: undefined, done: true }Generator有什么意義呢?
普通函數的執行會形成一個調用棧,入棧和出棧是一口氣完成的。而Generator必須得手動調用next()才能往下執行,相當于把執行的控制權從引擎交給了開發者。
所以Generator解決的是流程控制的問題。
它可以在執行過程暫時中斷,先執行別的程序,但是它的執行上下文并沒有銷毀,仍然可以在需要的時候切換回來,繼續往下執行。
最重要的優勢在于,它看起來是同步的語法,但是卻可以異步執行。
yield的作用
對于一個Generator函數來說,什么時候該暫停呢?就是在碰到yield關鍵字的時候。
function *gen() {console.log('a');yield 13 * 15;console.log('b');yield 15 - 13;console.log('c');return 3; }const it = gen();運行結果:
看上面的例子,第一次調用it.next()的時候,碰到了第一個yield關鍵字,然后開始計算yield后面表達式的值,然后這個值就成了it.next()返回值中value的值,然后停在這。這一步會打印a,但不會打印b。
以此類推。return的值作為最后一個狀態傳遞出去,然后返回值的done屬性就變成true,一旦它變成true,之后繼續執行的返回值都是沒有意義的。
這里面有一個狀態傳遞的過程。yield把它暫停之前獲得的狀態傳遞給執行器。
執行器傳遞狀態給狀態機內部
這有什么用?如果能在執行過程中給狀態機傳值,我們就可以改變狀態機的執行條件。你可以發現,Generator是可以實現值的雙向傳遞的。
為什么要作為上一個yield的返回值?你想啊,作為上一個yield的返回值,才能改變當前代碼的執行條件,這樣才有價值不是嘛。這地方有點繞,仔細想一想。
Generator自動執行
好吧,既然引擎把Generator的控制權交給了開發者,那我們就要探索出一種方法,讓Generator的遍歷器對象可以自動執行。
function* gen() {yield 1;yield 2;return 3; }function run(gen) {const it = gen();let state = { done: false };while (!state.done) {state = it.next();console.log(state);} }run(gen);但想想我們是來干什么的,我們是來探討JavaScript異步的呀。這個簡陋的run函數能夠執行異步操作嗎?
function fetchByName(name) {const url = `https://api.github.com/users/${name}/repos`;fetch(url).then(res => res.json()).then(res => console.log(res)); }function *gen() {yield fetchByName('veedrin');yield fetchByName('tj'); }function run(gen) {const it = gen();let state = { done: false };while (!state.done) {state = it.next();} }run(gen);事實證明,Generator會把fetchByName當做一個同步函數來執行,沒等請求觸發回調,它已經將指針指向了下一個yield。我們的目的是讓上一個異步任務完成以后才開始下一個異步任務,顯然這種方式做不到。
我們已經讓Generator自動化了,但是在面對異步任務的時候,交還控制權的時機依然不對。
Generator異步自動執行 ---- (在回調中交還控制權)
哪個時間點表明某個異步任務已經完成?當然是在回調中咯。
我們來拆解一下思路。
- 首先我們要把異步任務的其他參數和回調參數拆分開來,因為我們需要單獨在回調中扣一下扳機。
然后yield asyncTask()的返回值得是一個函數,它接受異步任務的回調作為參數。因為Generator只有yield的返回值是暴露在外面的,方便我們控制。
最后在回調中移動指針。
這就是把異步任務的其他參數和回調參數拆分開來的法寶。是不是很簡單?它通過兩層閉包將原過程變成三次函數調用,第一次傳入原函數,第二次傳入回調之前的參數,第三次傳入回調,并在最里一層閉包中又把參數整合起來傳入原函數。
是的,這就是大名鼎鼎的thunkify。
const fs = require('fs'); const thunkify = require('./thunkify');const readFileThunk = thunkify(fs.readFile);function *gen() {const valueA = yield readFileThunk('/Users/veedrin/a.md');console.log('a.md 的內容是:\n', valueA.toString());const valueB = yield readFileThunk('/Users/veedrin/b.md');console.log('b.md 的內容是:\n', valueB.toString()); }function run(gen) {const it = gen();function next(err, data) {const state = it.next(data);if (state.done) return;state.value(next);}next(); }run(gen);我們完全可以把回調函數抽象出來,每移動一次指針就遞歸一次,然后在回調函數內部加一個停止遞歸的邏輯,一個通用版的run函數就寫好啦。上例中的next()其實就是callback()呢。
使用co
co是一個真正的異步解決方案,因為它暴露的接口足夠簡單
import co from './co';function fetchByName(name) {const url = `https://api.github.com/users/${name}/repos`;return fetch(url).then(res => res.json()); }function *gen() {const value1 = yield fetchByName('veedrin');console.log(value1);const value2 = yield fetchByName('tj');console.log(value2); }co(gen);也許是終極異步解決方案
我知道你想說什么,寫一個異步調用還得引入一個npm包(雖然是大神TJ寫的包)
媽賣批的npm!
當然是不存在的。如果一個特性足夠重要,社區的呼聲足夠高,它就一定會被納入標準的。馬上我們要介紹的就是血統純正的異步編程家族終極繼承人——愛新覺羅·async。
封裝原理:
import co from 'co';function fetchByName(name) {const url = `https://api.github.com/users/${name}/repos`;return fetch(url).then(res => res.json()); }co(function *gen() {const value1 = yield fetchByName('veedrin');console.log(value1);const value2 = yield fetchByName('tj');console.log(value2); });使用:
function fetchByName(name) {const url = `https://api.github.com/users/${name}/repos`;return fetch(url).then(res => res.json()); }async function fetchData() {const value1 = await fetchByName('veedrin');console.log(value1);const value2 = await fetchByName('tj');console.log(value2); }fetchData();看看這無縫升級的體驗,嘖嘖
靈活使用async
別被新的關鍵字嚇到了,它其實非常靈活。
async function noop() {console.log('Easy, nothing happened.'); }復制代碼這家伙能執行嗎?當然能,老伙計還是你的老伙計。
async function noop() {const msg = await 'Easy, nothing happened.';console.log(msg); }復制代碼同樣別慌,還是預期的表現。
只有當await關鍵字后面是一個Promise的時候,它才會顯現它異步控制的威力,其余時候人畜無害。
function fetchByName(name) {const url = `https://api.github.com/users/${name}/repos`;return fetch(url).then(res => res.json()); }async function fetchData() {const name = await 'veedrin';const repos = await fetchByName(name);console.log(repos); }雖然說await關鍵字后面跟Promise或者非Promise都可以處理,但對它們的處理方式是不一樣的。非Promise表達式直接返回它的值就是了,而Promise表達式則會等待它的狀態從pending變為fulfilled,然后返回resolve的參數。它隱式的做了一下處理。
注意看,fetchByName('veedrin')按道理返回的是一個Promise實例,但是我們得到的repos值卻是一個數組,這里就是await關鍵字隱式處理的地方。
我們把它弄到一個作用域里去
import sleep from './sleep';function work() {[1, 2, 3].forEach(async v => {const rest = await sleep(3);console.log(rest);});return '睡醒了'; }work();不好意思,return '睡醒了’沒等異步操作完就執行了,這應該也不是你要的效果吧。
所以這種情況,只能用for循環來代替,async和await就能長相廝守了。
import sleep from './sleep';async function work() {const things = [1, 2, 3];for (let thing of things) {const rest = await sleep(3);console.log(rest);}return '睡醒了'; }work();返回Promise實例
async可不止一顆糖哦。它是Generator、co、Promise三者的封裝。如果說Generator只是一個狀態機的話,那async天生就是為異步而生的。
import sleep from './sleep';async function work() {const needRest = await sleep(6);const anotherRest = await sleep(3);console.log(needRest);console.log(anotherRest);return '睡醒了'; }work().then(res => console.log('?', res), res => console.error('?', res));因為async函數返回一個Promise實例,那它本身return的值跑哪去了呢?它成了返回的Promise實例resolve時傳遞的參數。也就是說return '睡醒了’在內部會轉成resolve(‘睡醒了’)。
我可以保證,返回的是一個真正的Promise實例,所以其他特性向Promise看齊就好了。
并發
也許你發現了,上一節的例子大概要等9秒多才能最終結束執行。可是兩個sleep之間并沒有依賴關系,你跟我說說我憑什么要等9秒多?
之前跟老子說要異步流程控制是不是!現在又跟老子說要并發是不是!
我…滿足你。
方法一:
import sleep from './sleep';async function work() {const needRest = await Promise.all([sleep(6), sleep(3)]);console.log(needRest);return '睡醒了'; }work().then(res => console.log('?', res), res => console.error('?', res)); import sleep from './sleep';async function work() {const onePromise = sleep(6);const anotherPromise = sleep(3);const needRest = await onePromise;const anotherRest = await anotherPromise;console.log(needRest);console.log(anotherRest);return '睡醒了'; }work().then(res => console.log('?', res), res => console.error('?', res));辦法也是有的,還不止一種。手段都差不多,就是把await往后挪,這樣既能摟的住,又能實現并發。
大總結
原文地址: https://juejin.im/post/5cda1492e51d453a8f348c02
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Generator简单了解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度贴吧app登录不了(登录百度帐号)
- 下一篇: 控制台一直报错, [WDS] Disco