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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

Generator简单了解

發布時間:2023/12/31 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的返回值是暴露在外面的,方便我們控制。
最后在回調中移動指針。

function thunkify(fn) {return (...args) => {return (done) => {args.push(done);fn(...args);}} }

這就是把異步任務的其他參數和回調參數拆分開來的法寶。是不是很簡單?它通過兩層閉包將原過程變成三次函數調用,第一次傳入原函數,第二次傳入回調之前的參數,第三次傳入回調,并在最里一層閉包中又把參數整合起來傳入原函數。

是的,這就是大名鼎鼎的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简单了解的全部內容,希望文章能夠幫你解決所遇到的問題。

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