欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS中Generator函數(shù)與async函數(shù)用法介紹

 更新時間:2023年06月07日 08:28:48   作者:剪刀石頭布啊  
javascript中經(jīng)常會用到異步編程,在ES6之后我們使用的?Generator函數(shù)、async函數(shù)、promise都是我們異步編程的一大助力,這里我們主要講解Generator、async函數(shù),并且簡介他們之間的一些聯(lián)系,本篇文章會帶著一些簡易案例,方便大家理解使用

Generator函數(shù)

Generator 函數(shù)看起來像指針函數(shù),實(shí)際上跟我們的普通函數(shù)差不多,有兩個特征

1、function關(guān)鍵字與函數(shù)名之間有一個星號

2、函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”),這里有暫停、等待執(zhí)行的意思

Generator函數(shù) 運(yùn)行后跟普通函數(shù)一樣,只不過碰到 yield 關(guān)鍵字會暫停,需要等待調(diào)用next 之后才會繼續(xù)往后執(zhí)行,直到遇到下一個 yieldreturn

yield 作為表達(dá)式一項(xiàng)時,需要使用 () 括起來,否則會報錯,例如: 2 * (yield x)

next 標(biāo)識著 generator 函數(shù)開始往后執(zhí)行語句,默認(rèn)執(zhí)行到 下一個 yield 語句,并返回 yield 后的內(nèi)容value 中,沒有遇到 return或者函數(shù)結(jié)束,done參數(shù)為false,否則done參數(shù)為 true

return 標(biāo)識著函數(shù)的結(jié)束,這里也不例外,只不過結(jié)束時返回的內(nèi)容也可以被 next 返回; 另外,不寫 return 則與return undefined類似

可以參考下面案例理解

function* generator() {
    yield 1
    yield 2
    yield 3
    yield 4
    return 5  // { value: 5, done: true },再往后 next 就和不寫一樣了
    // 不寫return,則與return undefined 一樣 返回 { value: undefined, done: true }
}
//獲取 generator 遍歷器
let g = generator()
let next = null
do {
    next = g.next() //開始往后執(zhí)行到下一個 yield 語句,并返回
    console.log(next)
}while(next?.done === false) //執(zhí)行到 return 或 函數(shù)結(jié)束 則停止打印
next = g.next()
console.log(next)
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// 有return 5 打印 { value: 5, done: true }, 沒有則打印 { value: undefined, done: true }

如果在 class 中怎么表示呢,里面是構(gòu)造函數(shù)的包裝,寫法不一樣, 只需要在前面加上一個即可

class A {
    //前面加上 * 即可
    * generator() {
        yield 1
        yield 2
    }
}
let a = new A()
let g = a.generator()
let next = g.next()
console.log(next)
next = g.next()
console.log(next)

generator 與 Iterator遍歷器

從上面打印就可以看出,generator函數(shù),實(shí)際上返回的是一個 Iterator遍歷器,因此我們亦可以通過遍歷器的手段來執(zhí)行 generator遍歷器

下面使用 for ... of 來遍歷一下我們上面的 generator遍歷器

//從上面就可以看出,generator函數(shù) 返回了一個 Iterator遍歷器對象
//我們遍歷一次試試
let g = generator()
for (let item of g) {
    console.log(item)
}
//結(jié)果打印 1 2 3 4

看其結(jié)果發(fā)現(xiàn)沒有打印最后的return,了解遍歷器就會知道,遍歷器遇到 donetrue時會直接結(jié)束(作為遍歷器使用的話,不要在 return 語句 放遍歷內(nèi)容)

我們再對比一下看看我們的 generator遍歷器是否真的是 Iteraotr遍歷器,發(fā)現(xiàn)一模一樣

//g[Symbol.iterator]() === g //遍歷器屬性一致,返回為 true,不信打印遍歷器屬性比較試試

next

前面介紹了,generator遍歷器調(diào)用 next(),會繼續(xù)執(zhí)行到下一個 yield語句

此外,next 可以傳參,我們傳遞的參數(shù)會作為 上一個 yield 返回的結(jié)果,沒有傳參,默認(rèn)返回都是 undefined

注意yield 返回的結(jié)果不是該表達(dá)式后面的內(nèi)容,且 yield 作為表達(dá)式一項(xiàng)時,需要使用 () 括起來,否則會報錯

下面看一下案例就知道結(jié)果了(過程已經(jīng)標(biāo)出)

//包含表達(dá)式時,yield 語句需要用括號括起來,否則會報錯
function* generator() {
    let num1 = yield 1
    console.log('num1', num1)
    let num2 = num1 * (yield 2)
    console.log('num2', num2)
    yield 3
    yield 4
}
//next傳入的值,作為上一個 yield 的返回值使用
let g = generator()
let next = g.next() //此時執(zhí)行到 yield 1,并包裝返回 yield 1 執(zhí)行的結(jié)果
console.log(next)  //{ value: 1, done: false }
//傳入 2, 當(dāng)做上一個語句的 yield 1 的返回值(不傳接收到的值均為undefined)
//即:返回 2 賦值給 num,然后執(zhí)行到 (yield 2),并包裝返回 (yield 2) 的結(jié)果
next = g.next(2) //執(zhí)行完畢后,打?。簄um1 2
console.log(next) //{ value: 2, done: false }
//傳入 3. 當(dāng)做上一個語句的 (yield 2) 的返回值(不傳接收到的值均為undefined)
//即:num2 = num1 * 3, 然后執(zhí)行到 yield 3,并包裝返回 yield 3 的結(jié)果
next = g.next(3)  //執(zhí)行完畢后,打印:num2 6
console.log(next) //{ value: 3, done: false }

由上述可以可以看到 next 傳參 挺好用的,除了上述,甚至可以使用傳遞的參數(shù),用于跳出死循環(huán)......

throw

這里的throw為通過 generator遍歷器拋出的異常,其異常會拋出到 generator 函數(shù)里面,因此需要注意拋出異常的位置,才能更好地配合try...catch使用

//throw()
// 相當(dāng)于在 generator 里面拋出了一個異常
function* generator() {
    try {
        yield 1
    }catch(err) {
        console.log(err)
    }
    yield 2
    yield 3
    yield 4
}
let g = generator()
let next = g.next()
console.log(next)
//執(zhí)行完畢 yield 1后,隨后在 yield 1 ~ yield 2 之間拋出異常,因此,try...catch 包裹主 yield 1 才行
let t = g.throw('啦啦啦') // 拋出異常并執(zhí)行到下一個 yield,如果沒處理錯誤,程序異常
console.log(t)
next = g.next()
console.log(next)

return

指定 return 會理立即結(jié)束 generator 函數(shù),并返回 { value: undefined, done: true },上面有介紹

function* generator() {
    yield 1
    yield 2
    yield 3
    yield 4
}
let g = generator()
next = g.next()
next = g.return() //直接返回 { value: undefined, done: true },如果傳參則返回return傳遞的參數(shù)
console.log(next) //會直接結(jié)束

另外,當(dāng)存在 try...finally 代碼塊時,并且執(zhí)行到 try 里面時return ,會仍然執(zhí)行finally 里面的語句,且會執(zhí)行到finally里面yield語句,finally執(zhí)行完畢,才是真正結(jié)束,并且return帶入的參數(shù),會應(yīng)用到 finally語句的末尾

function* generator() {
    yield 1
    try {
        //執(zhí)行到這里,外面 return 的話,也需要走完 finally 才會結(jié)束
        yield 2
        yield 3
    }finally {
        yield 7
        yield 8
    }
    yield 4
}
let g1 = generator()
next = g1.next() // { value: 1, done: false }
next = g1.next() // { value: 2, done: false }
next = g1.return(100) // { value: 7, done: false },return 后返回下一個,為finally中的 yield 7,這里順道返回一個值測試一下最后的返回值
next = g1.next() // { value: 8, done: false }
next = g1.next() // { value: 100, done: true }
next = g1.next() // { value: undefined, done: true }

yield*

yield*看著像個指針,其指向一個 generator遍歷器,會自動展開,不多說

//yield* 會展開 generator 函數(shù)
function* gen1() {
    yield 1
    yield 2
}
function* gen2() {
    yield 0
    yield* gen1()
}
//相當(dāng)于下面展開的語句,可以看出yield語句總是一個一個執(zhí)行的,即使嵌套也不會一次執(zhí)行一堆
function* gen3() {
    yield 0
    yield 1
    yield 2
}

generator應(yīng)用

generator應(yīng)用很多,這里簡單介紹兩種,算是小試牛刀

正常切換開關(guān),需要狀態(tài),這里通過 generator 遍歷器,避免了新增開關(guān)狀態(tài)(不適用于多方控制,例如:需要與后臺遠(yuǎn)程同步狀態(tài))

//切換狀態(tài)
function* toggleSwitch() {
    while(true) {
        console.log('開燈')
        yield 1
        console.log('關(guān)燈')
        yield 0
    }
}
//是不是切換很簡單了
let t = toggleSwitch()
let next = t.next() //開燈
console.log(next)
next = t.next() //關(guān)燈
console.log(next)

流程化管理,外部不用關(guān)心內(nèi)部(例如:加工軟件,不同工種對于自己的操作工序,完成后只需要點(diǎn)擊一下 next 即可)

function* order() {
    yield '收到訂單'
    yield '加工'
    yield '送檢'
    yield '質(zhì)檢'
    yield '發(fā)貨'
    yield '完成訂單'
}
let o = order()
let next = o.next() //收到訂單
next = o.next() //加工
next = o.next() //送檢
next = o.next() //質(zhì)檢
next = o.next() //發(fā)貨
next = o.next() //完成訂單

async、await函數(shù)

async、await,我們平時用的比較多,這里簡單介紹一下

async、await 就是根據(jù) generator函數(shù)改造而成,其改進(jìn)了 generator函數(shù)作為普通函數(shù)痛點(diǎn),其加入了執(zhí)行器,能像普通函數(shù)一樣直接執(zhí)行,更加方便,且語義更加清晰,結(jié)果返回一個 Promise(可以看出使用Promise包裝而成)

簡而言之async 聲明一個異步函數(shù),await 等待執(zhí)行,當(dāng)await語句執(zhí)行完畢后,才會執(zhí)行后面語句(出現(xiàn)錯誤直接拋出錯誤結(jié)束,不往后執(zhí)行),并且如果沒有 await 的存在,async函數(shù),跟一個普通的同步函數(shù)執(zhí)行順序沒有什么區(qū)別

ps: 關(guān)于 promise 前面有介紹,await執(zhí)行的過程與其一樣,實(shí)際上會馬上執(zhí)行任務(wù)隊(duì)列下一個任務(wù),前面的任務(wù)執(zhí)行完畢后,才會執(zhí)行到 await 后面的語句

簡單做一個使用案例,

async function a() {
    await promise1...
    await promise2...
    // 如果不return自己內(nèi)容,返回的則是 undefind
    return 1
}
function cc() {
    a().then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })
}

你可能會想,如果第一個 promise 出錯了怎么辦,第二個會執(zhí)行么?

答案:不會,第一個出錯后,會拋出一個異常,此時會結(jié)束解釋器的執(zhí)行,直接反饋錯誤到外部

下面改進(jìn)上一個案例

function a1() {
    return Promise.reject('出錯了 ')
}
function a2() {
    return Promise.resolve('成功了 ')
}
async function a() {
    await a1()
    console.log(11111111111)
    await a2()
    // 如果不return自己內(nèi)容,返回的則是 undefind
    return 1
}
function cc() {
    a().then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })
}
//結(jié)果是沒有執(zhí)行打印 11111111111,且執(zhí)行到了 .catch 中

下面我們模擬傳遞多個圖片的案例,可以看出 async、await 應(yīng)用多么方便

function uploadAImage(fileUrl) {
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            let res = fileUrl + '成功了'
            console.log(res)
            resolve(res)
        }, 500);
    })
}
async function uploadImages() {
    //準(zhǔn)備多個圖片url
    let fileUrl = ['url1', 'url2', 'url3']
    for (url of fileUrl) {
        try {
            await uploadAImage(url)
        }catch(err) {
            console.log(err)
            //實(shí)際上更復(fù)雜,成功的下次就不需要在上傳了
            return Promise.reject('存在上傳失敗的內(nèi)容') 
        }
    }
    return '都成功了'
}
uploadImages().then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

async、await 函數(shù)使用簡單就先介紹到這里了,后面會對 generator 進(jìn)行對比,是怎么改造了的

generator 模仿 async 自動執(zhí)行

前面說了 async函數(shù) 是通過 generator函數(shù) 改造而來的,里面添加了執(zhí)行器,我們自己嘗試一下做一個簡易的generator執(zhí)行器

這里是一個async 的默認(rèn)執(zhí)行案例

//使用 generator + 執(zhí)行器,簡單翻譯一下 async、await
async function asyncDefaultFunction() {
    let res = await new Promise((resolve) => {
        setTimeout(() => {
            resolve(1)
        }, 1000);
        // setTimeout(() => {
        //     reject('啦啦啦')
        // }, 1000);
    })
    return res
}
asyncDefaultFunction().then(res => {
    console.log(res)
})

我們使用 generator 改造成 async 自動執(zhí)行的模式

//使用 async 之后,實(shí)際上我們將返回函數(shù)包裝一下即可,并且將 yield 就是 await
function asyncFunction() {
    return co(function* () {
        //async 函數(shù)里面的內(nèi)容,將 yield 與 await 替換即可
        let res = yield new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            }, 1000);
            // setTimeout(() => {
            //     reject('啦啦啦')
            // }, 1000);
        })
        return res
    })
}
//編寫一個自動執(zhí)行 generator 的 co 函數(shù)
function co(genFunc) {
    return new Promise(function (resolve, reject) {
        const g = genFunc() //獲取generator,準(zhǔn)備執(zhí)行
        function nextFunc(value) {
            let next;
            try {
                next = g.next(value)
            }catch(err) {
                return reject(err)
            }
            if (next.done) {
                return resolve(next.value)
            }
            //為什么要promise包裝,語句可能為同步函數(shù),可能為異步函數(shù),木事保證正確執(zhí)行
            //如果為 promise 直接原封不動返回,如果為普通對象則包裝,具體見promise
            Promise.resolve(next.value).then(function(res) {
                nextFunc(res)
            }, function(err) {
                reject(err)
            })
        }
        nextFunc(undefined)
    })
}
asyncFunction().then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

這樣就實(shí)現(xiàn)了一個簡易的 generator 自動執(zhí)行器

同時我們也發(fā)現(xiàn)了,async 與我們普通函數(shù)相比,加劇了負(fù)擔(dān),為了優(yōu)化性能,我們平時如果沒有用到異步函數(shù)(或者連 await 都用不到的),將多余的 async 去掉吧,這樣某種程度上能夠避免性能浪費(fèi),尤其是到了循環(huán)語句

最后

看到了這里,相信我們也能學(xué)習(xí)到不少東西,可以想一下,其他語言是不是也是類似這樣呢,遇到了相似的情況我們是不是也豁然開朗了呢,很多語言是互通的,學(xué)習(xí)就是積累的過程,希望我們此次能有所收獲!

以上就是JS中Generator函數(shù)與async函數(shù)用法介紹的詳細(xì)內(nèi)容,更多關(guān)于JS Generator函數(shù)與async函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論