JavaScript生成器函數(shù)Generator解決異步操作問題
為什么使用Generator?
在JavaScript使用異步操作時,在async和await還沒有被JavaScript官方正式推出時,那么異步操作解決方案就只有回調(diào)函數(shù)和Promise。
回調(diào)函數(shù)
所謂回調(diào)函數(shù),就是把需要執(zhí)行的動作以函數(shù)的方式包裝起來,再將這個函數(shù)以參數(shù)的方式傳遞給其他的函數(shù),當(dāng)時機到來時再進行調(diào)用。
// 需在瀏覽器中運行 function loadImage(imgUrl, callback) { const img = document.createElement("img"); img.onload = function () { callback(this); }; img.src = imgUrl; } loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/1716878f-79a2-4db1-af8c-b9c2039f0b3c_product_W572_H370.jpg", (img) => { document.body.appendChild(img); loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/8b36f9a7-f780-4e71-b719-9300109a9ff2_product_W572_H370.jpg", (img) => { document.body.appendChild(img); loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/6d77d0ea-53d0-4518-b7e9-e53795b4920c_product_W572_H370.jpg", (img) => { document.body.appendChild(img); } ); } ); } );
Promise
promise是為了解決回調(diào)函數(shù)產(chǎn)生的回調(diào)地獄問題而產(chǎn)生的。
function loadImage(imgUrl) { return new Promise((resolve) => { const img = document.createElement("img"); img.onload = function () { resolve(this); }; img.src = imgUrl; }); } loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/1716878f-79a2-4db1-af8c-b9c2039f0b3c_product_W572_H370.jpg" ) .then((img) => { document.body.appendChild(img); return loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/8b36f9a7-f780-4e71-b719-9300109a9ff2_product_W572_H370.jpg" ); }) .then((img) => { document.body.appendChild(img); return loadImage( "https://travel.12306.cn/imgs/resources/uploadfiles/images/6d77d0ea-53d0-4518-b7e9-e53795b4920c_product_W572_H370.jpg" ); }) .then((img) => { document.body.appendChild(img); });
雖然解決了回調(diào)地獄的問題,異步任務(wù)執(zhí)行步驟也更加清晰了,但是相比于現(xiàn)在async和await的異步操作同步化的表達方式還是略遜一籌,在async和await還沒有推出時,社區(qū)就已經(jīng)利用生成器函數(shù)實現(xiàn)了社區(qū)版的async和await,當(dāng)時生成器的出現(xiàn)就是為了服務(wù)于異步編程。
使用方式
創(chuàng)建方式
function* getValue() { yield 1; yield 2; return 3; } const generator = getValue() // 獲取到生成器 console.log(Object.prototype.toString.call(generator)); // [object Generator]
next方法
獲取返回值
生成器函數(shù)和普通函數(shù)的調(diào)用方式完全不同,上面代碼雖然調(diào)用生成器函數(shù)產(chǎn)生了生成器,但是生成器函數(shù)內(nèi)部的代碼并為被執(zhí)行。
那么需要怎樣操作才能讓生成器內(nèi)部的代碼執(zhí)行?
生成器有一個next方法,當(dāng)這個方法被調(diào)用時,會把yield后面的值返回回來,并且生成器函數(shù)內(nèi)部的代碼停止執(zhí)行,當(dāng)再次調(diào)用next方法后,生成器函數(shù)內(nèi)部代碼會從上次暫停處開始執(zhí)行,到下一個yield語句處停止。
next()方法返回的對象包含兩個屬性:
- value:返回的值,也就是yield關(guān)鍵字后面的值。
done:表示生成器函數(shù)是否已經(jīng)完成,true表示已經(jīng)完成,false表示未完成。
function* getValue() { yield 1; yield 2; yield 3; } const generator = getValue(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: undefined, done: true }
當(dāng)生成器內(nèi)最后的yield語句也執(zhí)行過后,意味著整個生成器函數(shù)執(zhí)行完畢,調(diào)用next()方法產(chǎn)出的結(jié)果中done的值為true了。
簡單用圖片演示下運行流程:
第一次調(diào)用next()方法,生成器函數(shù)會暫停在第一個yield語句
再次調(diào)用next()方法,生成器函數(shù)會暫停在第二個yield語句
再次調(diào)用next()方法,生成器函數(shù)會暫停在第三個yield語句
再次調(diào)用next()方法,生成器函數(shù)執(zhí)行完畢
這里返回的value為什么是undefined,其實可以這么理解,因為在JavaScript中,函數(shù)不主動return,那么會默認(rèn)返回undefined。
function* getValue() { yield 1; yield 2; yield 3; return undefined; }
如果生成器函數(shù)中有return語句,那么在執(zhí)行return語句的時候就會把生成器的狀態(tài)置為已完成,后面的yield語句將不再被執(zhí)行,這點和普通函數(shù)是保持一致的。
function* getValue() { yield 1; yield 2; return 3; } const generator = getValue(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: true }
傳遞參數(shù)
next函數(shù)其實也可以傳遞參數(shù),這個參數(shù)將被yield語句消費。
function* getValue() { const a = yield 1; const b = yield 2 * a; return 3 * b; } const generator = getValue(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next(5)); // { value: 10, done: false } console.log(generator.next(6)); // { value: 18, done: true }
還是使用圖片來演示整個流程。
- 首先調(diào)用next(),語句在第一個yield語句處暫停
- 然后調(diào)用next(5),參數(shù)5會傳遞給yield語句等號左邊的變量a,語句會停在第二個yield語句處
- 然后調(diào)用next(6),參數(shù)6會傳遞給yield語句等號左邊的變量b,碰到return語句直接返回,整個生成器函數(shù)執(zhí)行完畢。
throw函數(shù)
next函數(shù)可以往生成器里面?zhèn)鬟f函數(shù),而throw方法可以往生成器函數(shù)里面拋出異常,如果沒有在生成器函數(shù)里面捕獲異常,那么生成器函數(shù)會向其它函數(shù)一樣拋出異常。
function* getValue() { yield 1; try { yield 2; } catch (error) { console.error(error); // 捕獲異常, 打印 trhow error } yield 3; } const generator = getValue(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next(5)); // { value: 2, done: false } console.log(generator.throw("throw error")); // {value: 3, done: false}
return函數(shù)
reutrn函數(shù)在被調(diào)用后,生成器函數(shù)會直接返回return函數(shù)傳遞的值,而且生成器函數(shù)整個函數(shù)執(zhí)行完畢。
function* getValue() { yield 1; yield 2; yield 3; } const generator = getValue(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.return(5)); // { value: 5, done: true } // 此時生成器函數(shù)執(zhí)行完畢 console.log(generator.next()); // {value: undefined, done: true}
使用場景
實現(xiàn)Async和Await
function getValue(n, ms = 1000) { return new Promise((resolve) => { setTimeout(() => { resolve(n); }, ms); }); } function* generator() { console.log(1); let x = yield getValue(2); console.log(x); x = yield getValue(3, 2000); console.log(x); x = yield 4; console.log(x); } function asyncGenerator(generator) { return new Promise((resolve, reject) => { let iterable = generator(); let generated = iterable.next(); tick(); function tick() { if (generated.done === false) { Promise.resolve(generated.value).then( (value) => { try { generated = iterable.next(value); tick(); } catch (err) { reject(err); } }, (reason) => { try { generated = iterable.throw(reason); tick(); } catch (err) { reject(err); } } ); } else { resolve(generated.value); } } }); } asyncGenerator(generator);
借用上面實現(xiàn)asyncGenerator函數(shù),我們可以再來優(yōu)化下加載圖片的代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body></body> <script> const imgUrlList = [ "https://travel.12306.cn/imgs/resources/uploadfiles/images/1716878f-79a2-4db1-af8c-b9c2039f0b3c_product_W572_H370.jpg", "https://travel.12306.cn/imgs/resources/uploadfiles/images/8b36f9a7-f780-4e71-b719-9300109a9ff2_product_W572_H370.jpg", "https://travel.12306.cn/imgs/resources/uploadfiles/images/6d77d0ea-53d0-4518-b7e9-e53795b4920c_product_W572_H370.jpg", ]; function loadImage(imgUrl) { return new Promise((resolve) => { const img = document.createElement("img"); img.onload = function () { resolve(this); }; img.src = imgUrl; }); } function* loadImageList() { const img1 = yield loadImage(imgUrlList[0]); document.body.appendChild(img1); const img2 = yield loadImage(imgUrlList[1]); document.body.appendChild(img2); const img3 = yield loadImage(imgUrlList[2]); document.body.appendChild(img3); } function asyncGenerator(generator) { return new Promise((resolve, reject) => { let iterable = generator(); let generated = iterable.next(); tick(); function tick() { if (generated.done === false) { Promise.resolve(generated.value).then( (value) => { try { generated = iterable.next(value); tick(); } catch (err) { reject(err); } }, (reason) => { try { generated = iterable.throw(reason); tick(); } catch (err) { reject(err); } } ); } else { resolve(generated.value); } } }); } asyncGenerator(loadImageList).then(() => { console.log("load success"); }); </script> </html>
實現(xiàn)自定義迭代器
const list = { head: { value: 1, next: { value: 2, next: { value: 3, next: null, }, }, }, *[Symbol.iterator]() { let curNode = list.head; while (curNode) { yield curNode.value; curNode = curNode.next; } }, }; for (let val of list) { console.log(val); }
上面針對鏈表數(shù)據(jù)結(jié)構(gòu)使用for...of的進行遍歷,這得益于在list內(nèi)部聲明了迭代器,有兩點是for...of所需要的
- 返回包含next方法的對象
- 調(diào)用返回的next方法返回的對象包含value和done這兩個屬性
而這兩點生成器函數(shù)全部滿足,無疑是天作之合。
以上就是JavaScript生成器函數(shù)Generator解決異步操作問題的詳細(xì)內(nèi)容,更多關(guān)于JavaScript生成器函數(shù)Generator的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript打印html內(nèi)容功能的方法示例
這篇文章主要介紹了javascript打印html內(nèi)容的小示例,大家參考使用2013-11-11